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.
- ophyd_async/_version.py +2 -2
- ophyd_async/core/__init__.py +86 -73
- ophyd_async/core/{detector.py → _detector.py} +42 -36
- ophyd_async/core/{device.py → _device.py} +1 -1
- ophyd_async/core/{device_save_loader.py → _device_save_loader.py} +3 -3
- ophyd_async/core/{flyer.py → _flyer.py} +6 -8
- ophyd_async/{epics/areadetector/writers/general_hdffile.py → core/_hdf_dataset.py} +4 -8
- ophyd_async/{log.py → core/_log.py} +11 -3
- ophyd_async/core/{mock_signal_backend.py → _mock_signal_backend.py} +3 -3
- ophyd_async/core/{mock_signal_utils.py → _mock_signal_utils.py} +7 -7
- ophyd_async/{protocols.py → core/_protocol.py} +1 -1
- ophyd_async/core/_providers.py +24 -37
- ophyd_async/core/{standard_readable.py → _readable.py} +6 -16
- ophyd_async/core/{signal.py → _signal.py} +79 -35
- ophyd_async/core/{signal_backend.py → _signal_backend.py} +4 -13
- ophyd_async/core/{soft_signal_backend.py → _soft_signal_backend.py} +3 -12
- ophyd_async/core/{async_status.py → _status.py} +3 -11
- ophyd_async/epics/adaravis/__init__.py +9 -0
- ophyd_async/epics/{areadetector/aravis.py → adaravis/_aravis.py} +9 -11
- ophyd_async/epics/{areadetector/controllers/aravis_controller.py → adaravis/_aravis_controller.py} +7 -10
- ophyd_async/epics/{areadetector/drivers/aravis_driver.py → adaravis/_aravis_io.py} +6 -3
- ophyd_async/epics/adcore/__init__.py +47 -0
- ophyd_async/epics/adcore/_core_io.py +138 -0
- ophyd_async/epics/{areadetector/drivers/ad_base.py → adcore/_core_logic.py} +16 -52
- ophyd_async/epics/{areadetector/writers/hdf_writer.py → adcore/_hdf_writer.py} +54 -29
- ophyd_async/epics/{areadetector/single_trigger_det.py → adcore/_single_trigger.py} +5 -6
- ophyd_async/epics/adcore/_utils.py +132 -0
- ophyd_async/epics/adkinetix/__init__.py +9 -0
- ophyd_async/epics/{areadetector/kinetix.py → adkinetix/_kinetix.py} +9 -11
- ophyd_async/epics/{areadetector/controllers/kinetix_controller.py → adkinetix/_kinetix_controller.py} +6 -9
- ophyd_async/epics/{areadetector/drivers/kinetix_driver.py → adkinetix/_kinetix_io.py} +9 -5
- ophyd_async/epics/adpilatus/__init__.py +11 -0
- ophyd_async/epics/{areadetector/pilatus.py → adpilatus/_pilatus.py} +10 -14
- ophyd_async/epics/{areadetector/controllers/pilatus_controller.py → adpilatus/_pilatus_controller.py} +15 -17
- ophyd_async/epics/{areadetector/drivers/pilatus_driver.py → adpilatus/_pilatus_io.py} +6 -4
- ophyd_async/epics/adsimdetector/__init__.py +7 -0
- ophyd_async/epics/{demo/demo_ad_sim_detector.py → adsimdetector/_sim.py} +10 -11
- ophyd_async/epics/{areadetector/controllers/ad_sim_controller.py → adsimdetector/_sim_controller.py} +8 -14
- ophyd_async/epics/advimba/__init__.py +9 -0
- ophyd_async/epics/{areadetector/vimba.py → advimba/_vimba.py} +9 -9
- ophyd_async/epics/{areadetector/controllers/vimba_controller.py → advimba/_vimba_controller.py} +9 -17
- ophyd_async/epics/{areadetector/drivers/vimba_driver.py → advimba/_vimba_io.py} +11 -8
- ophyd_async/epics/demo/__init__.py +9 -132
- ophyd_async/epics/demo/_mover.py +97 -0
- ophyd_async/epics/demo/_sensor.py +36 -0
- ophyd_async/epics/{motion/motor.py → motor.py} +28 -14
- ophyd_async/epics/pvi/__init__.py +2 -2
- ophyd_async/epics/pvi/{pvi.py → _pvi.py} +17 -14
- ophyd_async/epics/signal/__init__.py +7 -1
- ophyd_async/epics/{_backend → signal}/_aioca.py +15 -7
- ophyd_async/epics/{_backend/common.py → signal/_common.py} +2 -2
- ophyd_async/epics/signal/_epics_transport.py +3 -3
- ophyd_async/epics/{_backend → signal}/_p4p.py +18 -14
- ophyd_async/epics/signal/{signal.py → _signal.py} +10 -9
- ophyd_async/fastcs/odin/__init__.py +0 -0
- ophyd_async/{panda → fastcs/panda}/__init__.py +20 -15
- ophyd_async/{panda/_common_blocks.py → fastcs/panda/_block.py} +5 -3
- ophyd_async/{panda/_panda_controller.py → fastcs/panda/_control.py} +2 -1
- ophyd_async/{panda → fastcs/panda}/_hdf_panda.py +5 -10
- ophyd_async/{panda → fastcs/panda}/_trigger.py +3 -7
- ophyd_async/{panda/writers/_hdf_writer.py → fastcs/panda/_writer.py} +36 -28
- ophyd_async/plan_stubs/__init__.py +5 -2
- ophyd_async/plan_stubs/{ensure_connected.py → _ensure_connected.py} +1 -2
- ophyd_async/plan_stubs/{fly.py → _fly.py} +13 -9
- ophyd_async/plan_stubs/_nd_attributes.py +63 -0
- ophyd_async/sim/__init__.py +0 -11
- ophyd_async/sim/demo/__init__.py +18 -2
- ophyd_async/sim/demo/_pattern_detector/__init__.py +13 -0
- ophyd_async/sim/{sim_pattern_generator.py → demo/_pattern_detector/_pattern_detector.py} +8 -8
- ophyd_async/sim/{sim_pattern_detector_control.py → demo/_pattern_detector/_pattern_detector_controller.py} +9 -7
- ophyd_async/sim/{sim_pattern_detector_writer.py → demo/_pattern_detector/_pattern_detector_writer.py} +4 -4
- ophyd_async/sim/{pattern_generator.py → demo/_pattern_detector/_pattern_generator.py} +13 -11
- ophyd_async/sim/demo/{sim_motor.py → _sim_motor.py} +7 -5
- ophyd_async/sim/testing/__init__.py +0 -0
- ophyd_async/tango/__init__.py +0 -0
- {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/METADATA +46 -44
- ophyd_async-0.5.1.dist-info/RECORD +90 -0
- {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/WHEEL +1 -1
- ophyd_async/epics/areadetector/__init__.py +0 -23
- ophyd_async/epics/areadetector/controllers/__init__.py +0 -5
- ophyd_async/epics/areadetector/drivers/__init__.py +0 -23
- ophyd_async/epics/areadetector/utils.py +0 -104
- ophyd_async/epics/areadetector/writers/__init__.py +0 -5
- ophyd_async/epics/areadetector/writers/nd_file_hdf.py +0 -43
- ophyd_async/epics/areadetector/writers/nd_plugin.py +0 -68
- ophyd_async/epics/motion/__init__.py +0 -3
- ophyd_async/panda/writers/__init__.py +0 -3
- ophyd_async-0.4.0.dist-info/RECORD +0 -84
- /ophyd_async/core/{utils.py → _utils.py} +0 -0
- /ophyd_async/{epics/_backend → fastcs}/__init__.py +0 -0
- /ophyd_async/{panda → fastcs/panda}/_table.py +0 -0
- /ophyd_async/{panda → fastcs/panda}/_utils.py +0 -0
- {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/LICENSE +0 -0
- {ophyd_async-0.4.0.dist-info → ophyd_async-0.5.1.dist-info}/entry_points.txt +0 -0
- {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
|
|
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
|
|
45
|
-
def __init__(self,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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 .
|
|
20
|
-
from .
|
|
21
|
-
|
|
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
|
|
31
|
+
class ADHDFWriter(DetectorWriter):
|
|
25
32
|
def __init__(
|
|
26
33
|
self,
|
|
27
|
-
hdf:
|
|
34
|
+
hdf: NDFileHDFIO,
|
|
28
35
|
path_provider: PathProvider,
|
|
29
36
|
name_provider: NameProvider,
|
|
30
37
|
shape_provider: ShapeProvider,
|
|
31
|
-
|
|
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
|
-
|
|
44
|
+
|
|
45
|
+
self._plugins = plugins
|
|
38
46
|
self._capture_status: Optional[AsyncStatus] = None
|
|
39
|
-
self._datasets: List[
|
|
40
|
-
self._file: Optional[
|
|
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
|
-
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
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
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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 =
|
|
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(
|
|
152
|
-
await wait_for_value(self.hdf.capture,
|
|
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 .
|
|
15
|
-
from .
|
|
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
|
|
18
|
+
class SingleTriggerDetector(StandardReadable, Triggerable):
|
|
20
19
|
def __init__(
|
|
21
20
|
self,
|
|
22
|
-
drv:
|
|
21
|
+
drv: ADBaseIO,
|
|
23
22
|
read_uncached: Sequence[SignalR] = (),
|
|
24
23
|
name="",
|
|
25
|
-
**plugins:
|
|
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)
|
|
@@ -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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from
|
|
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:
|
|
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 =
|
|
30
|
-
self.hdf =
|
|
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
|
-
|
|
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
|
|
6
|
-
start_acquiring_driver_and_ensure_status,
|
|
7
|
-
)
|
|
5
|
+
from ophyd_async.epics import adcore
|
|
8
6
|
|
|
9
|
-
from
|
|
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:
|
|
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)
|