ophyd-async 0.9.0a2__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.
- ophyd_async/__init__.py +5 -8
- ophyd_async/_docs_parser.py +12 -0
- ophyd_async/_version.py +9 -4
- ophyd_async/core/__init__.py +97 -62
- ophyd_async/core/_derived_signal.py +271 -0
- ophyd_async/core/_derived_signal_backend.py +300 -0
- ophyd_async/core/_detector.py +106 -125
- ophyd_async/core/_device.py +69 -63
- ophyd_async/core/_device_filler.py +65 -1
- ophyd_async/core/_flyer.py +14 -5
- ophyd_async/core/_hdf_dataset.py +29 -22
- ophyd_async/core/_log.py +14 -23
- ophyd_async/core/_mock_signal_backend.py +11 -3
- ophyd_async/core/_protocol.py +65 -45
- ophyd_async/core/_providers.py +28 -9
- ophyd_async/core/_readable.py +44 -35
- ophyd_async/core/_settings.py +36 -27
- ophyd_async/core/_signal.py +262 -170
- ophyd_async/core/_signal_backend.py +56 -13
- ophyd_async/core/_soft_signal_backend.py +16 -11
- ophyd_async/core/_status.py +72 -24
- ophyd_async/core/_table.py +37 -8
- ophyd_async/core/_utils.py +96 -49
- ophyd_async/core/_yaml_settings.py +2 -0
- ophyd_async/epics/__init__.py +1 -0
- ophyd_async/epics/adandor/_andor.py +2 -2
- ophyd_async/epics/adandor/_andor_controller.py +4 -2
- ophyd_async/epics/adandor/_andor_io.py +2 -4
- ophyd_async/epics/adaravis/__init__.py +5 -0
- ophyd_async/epics/adaravis/_aravis.py +4 -8
- ophyd_async/epics/adaravis/_aravis_controller.py +20 -43
- ophyd_async/epics/adaravis/_aravis_io.py +13 -28
- ophyd_async/epics/adcore/__init__.py +23 -8
- ophyd_async/epics/adcore/_core_detector.py +42 -2
- ophyd_async/epics/adcore/_core_io.py +124 -99
- ophyd_async/epics/adcore/_core_logic.py +106 -27
- ophyd_async/epics/adcore/_core_writer.py +12 -8
- ophyd_async/epics/adcore/_hdf_writer.py +21 -38
- ophyd_async/epics/adcore/_single_trigger.py +2 -2
- ophyd_async/epics/adcore/_utils.py +2 -2
- ophyd_async/epics/adkinetix/__init__.py +2 -1
- ophyd_async/epics/adkinetix/_kinetix.py +3 -3
- ophyd_async/epics/adkinetix/_kinetix_controller.py +4 -2
- ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
- ophyd_async/epics/adpilatus/__init__.py +5 -0
- ophyd_async/epics/adpilatus/_pilatus.py +1 -1
- ophyd_async/epics/adpilatus/_pilatus_controller.py +5 -24
- ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
- ophyd_async/epics/adsimdetector/__init__.py +8 -1
- ophyd_async/epics/adsimdetector/_sim.py +4 -14
- ophyd_async/epics/adsimdetector/_sim_controller.py +17 -0
- ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
- ophyd_async/epics/advimba/__init__.py +10 -1
- ophyd_async/epics/advimba/_vimba.py +3 -2
- ophyd_async/epics/advimba/_vimba_controller.py +4 -2
- ophyd_async/epics/advimba/_vimba_io.py +23 -28
- ophyd_async/epics/core/_aioca.py +35 -16
- ophyd_async/epics/core/_epics_connector.py +4 -0
- ophyd_async/epics/core/_epics_device.py +2 -0
- ophyd_async/epics/core/_p4p.py +10 -2
- ophyd_async/epics/core/_pvi_connector.py +65 -8
- ophyd_async/epics/core/_signal.py +51 -51
- ophyd_async/epics/core/_util.py +4 -4
- ophyd_async/epics/demo/__init__.py +16 -0
- ophyd_async/epics/demo/__main__.py +31 -0
- ophyd_async/epics/demo/_ioc.py +32 -0
- ophyd_async/epics/demo/_motor.py +82 -0
- ophyd_async/epics/demo/_point_detector.py +42 -0
- ophyd_async/epics/demo/_point_detector_channel.py +22 -0
- ophyd_async/epics/demo/_stage.py +15 -0
- ophyd_async/epics/{sim/mover.db → demo/motor.db} +2 -1
- ophyd_async/epics/demo/point_detector.db +59 -0
- ophyd_async/epics/demo/point_detector_channel.db +21 -0
- ophyd_async/epics/eiger/_eiger.py +1 -3
- ophyd_async/epics/eiger/_eiger_controller.py +11 -4
- ophyd_async/epics/eiger/_eiger_io.py +2 -0
- ophyd_async/epics/eiger/_odin_io.py +1 -2
- ophyd_async/epics/motor.py +65 -28
- ophyd_async/epics/signal.py +4 -1
- ophyd_async/epics/testing/_example_ioc.py +21 -9
- ophyd_async/epics/testing/_utils.py +3 -0
- ophyd_async/epics/testing/test_records.db +8 -0
- ophyd_async/epics/testing/test_records_pva.db +17 -16
- ophyd_async/fastcs/__init__.py +1 -0
- ophyd_async/fastcs/core.py +6 -0
- ophyd_async/fastcs/odin/__init__.py +1 -0
- ophyd_async/fastcs/panda/__init__.py +8 -6
- ophyd_async/fastcs/panda/_block.py +29 -9
- ophyd_async/fastcs/panda/_control.py +5 -0
- ophyd_async/fastcs/panda/_hdf_panda.py +2 -0
- ophyd_async/fastcs/panda/_table.py +9 -6
- ophyd_async/fastcs/panda/_trigger.py +23 -9
- ophyd_async/fastcs/panda/_writer.py +27 -30
- ophyd_async/plan_stubs/__init__.py +2 -0
- ophyd_async/plan_stubs/_ensure_connected.py +1 -0
- ophyd_async/plan_stubs/_fly.py +2 -4
- ophyd_async/plan_stubs/_nd_attributes.py +2 -0
- ophyd_async/plan_stubs/_panda.py +1 -0
- ophyd_async/plan_stubs/_settings.py +43 -16
- ophyd_async/plan_stubs/_utils.py +3 -0
- ophyd_async/plan_stubs/_wait_for_awaitable.py +1 -1
- ophyd_async/sim/__init__.py +24 -14
- ophyd_async/sim/__main__.py +43 -0
- ophyd_async/sim/_blob_detector.py +33 -0
- ophyd_async/sim/_blob_detector_controller.py +48 -0
- ophyd_async/sim/_blob_detector_writer.py +105 -0
- ophyd_async/sim/_mirror_horizontal.py +46 -0
- ophyd_async/sim/_mirror_vertical.py +74 -0
- ophyd_async/sim/_motor.py +233 -0
- ophyd_async/sim/_pattern_generator.py +124 -0
- ophyd_async/sim/_point_detector.py +86 -0
- ophyd_async/sim/_stage.py +19 -0
- ophyd_async/tango/__init__.py +1 -0
- ophyd_async/tango/core/__init__.py +6 -1
- ophyd_async/tango/core/_base_device.py +41 -33
- ophyd_async/tango/core/_converters.py +81 -0
- ophyd_async/tango/core/_signal.py +18 -32
- ophyd_async/tango/core/_tango_readable.py +2 -19
- ophyd_async/tango/core/_tango_transport.py +136 -60
- ophyd_async/tango/core/_utils.py +47 -0
- ophyd_async/tango/{sim → demo}/_counter.py +2 -0
- ophyd_async/tango/{sim → demo}/_detector.py +2 -0
- ophyd_async/tango/{sim → demo}/_mover.py +5 -4
- ophyd_async/tango/{sim → demo}/_tango/_servers.py +4 -0
- ophyd_async/tango/testing/__init__.py +6 -0
- ophyd_async/tango/testing/_one_of_everything.py +200 -0
- ophyd_async/testing/__init__.py +29 -7
- ophyd_async/testing/_assert.py +137 -81
- ophyd_async/testing/_mock_signal_utils.py +56 -70
- ophyd_async/testing/_one_of_everything.py +41 -21
- ophyd_async/testing/_single_derived.py +87 -0
- ophyd_async/testing/_utils.py +3 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info}/METADATA +25 -26
- ophyd_async-0.10.0a1.dist-info/RECORD +149 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info}/WHEEL +1 -1
- ophyd_async/epics/sim/__init__.py +0 -54
- ophyd_async/epics/sim/_ioc.py +0 -29
- ophyd_async/epics/sim/_mover.py +0 -101
- ophyd_async/epics/sim/_sensor.py +0 -37
- ophyd_async/epics/sim/sensor.db +0 -19
- ophyd_async/sim/_pattern_detector/__init__.py +0 -13
- ophyd_async/sim/_pattern_detector/_pattern_detector.py +0 -42
- ophyd_async/sim/_pattern_detector/_pattern_detector_controller.py +0 -69
- ophyd_async/sim/_pattern_detector/_pattern_detector_writer.py +0 -41
- ophyd_async/sim/_pattern_detector/_pattern_generator.py +0 -214
- ophyd_async/sim/_sim_motor.py +0 -107
- ophyd_async-0.9.0a2.dist-info/RECORD +0 -129
- /ophyd_async/tango/{sim → demo}/__init__.py +0 -0
- /ophyd_async/tango/{sim → demo}/_tango/__init__.py +0 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info/licenses}/LICENSE +0 -0
- {ophyd_async-0.9.0a2.dist-info → ophyd_async-0.10.0a1.dist-info}/top_level.txt +0 -0
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
from typing import Annotated as A
|
|
2
3
|
|
|
3
|
-
from ophyd_async.core import
|
|
4
|
-
from ophyd_async.core
|
|
5
|
-
from ophyd_async.epics.core import (
|
|
6
|
-
epics_signal_r,
|
|
7
|
-
epics_signal_rw,
|
|
8
|
-
epics_signal_rw_rbv,
|
|
9
|
-
)
|
|
4
|
+
from ophyd_async.core import DatasetDescriber, SignalR, SignalRW, StrictEnum
|
|
5
|
+
from ophyd_async.epics.core import EpicsDevice, PvSuffix
|
|
10
6
|
|
|
11
|
-
from ._utils import ADBaseDataType,
|
|
7
|
+
from ._utils import ADBaseDataType, ADFileWriteMode, ADImageMode, convert_ad_dtype_to_np
|
|
12
8
|
|
|
13
9
|
|
|
14
|
-
class
|
|
10
|
+
class ADCallbacks(StrictEnum):
|
|
15
11
|
ENABLE = "Enable"
|
|
16
12
|
DISABLE = "Disable"
|
|
17
13
|
|
|
18
14
|
|
|
19
|
-
class NDArrayBaseIO(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
15
|
+
class NDArrayBaseIO(EpicsDevice):
|
|
16
|
+
"""Class responsible for passing detector data from drivers to pluglins.
|
|
17
|
+
|
|
18
|
+
This mirrors the interface provided by ADCore/db/NDArrayBase.template.
|
|
19
|
+
See HTML docs at https://areadetector.github.io/areaDetector/ADCore/NDArray.html
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
unique_id: A[SignalR[int], PvSuffix("UniqueId_RBV")]
|
|
23
|
+
nd_attributes_file: A[SignalRW[str], PvSuffix("NDAttributesFile")]
|
|
24
|
+
acquire: A[SignalRW[bool], PvSuffix.rbv("Acquire")]
|
|
25
|
+
array_size_x: A[SignalR[int], PvSuffix("ArraySizeX_RBV")]
|
|
26
|
+
array_size_y: A[SignalR[int], PvSuffix("ArraySizeY_RBV")]
|
|
27
|
+
data_type: A[SignalR[ADBaseDataType], PvSuffix("DataType_RBV")]
|
|
28
|
+
array_counter: A[SignalRW[int], PvSuffix.rbv("ArrayCounter")]
|
|
29
|
+
# There is no _RBV for this one
|
|
30
|
+
wait_for_plugins: A[SignalRW[bool], PvSuffix("WaitForPlugins")]
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class ADBaseDatasetDescriber(DatasetDescriber):
|
|
@@ -46,49 +46,51 @@ class ADBaseDatasetDescriber(DatasetDescriber):
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
class NDPluginBaseIO(NDArrayBaseIO):
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
"""Base class from which plugins are derived.
|
|
50
|
+
|
|
51
|
+
This mirrors the interface provided by ADCore/db/NDPluginBase.template.
|
|
52
|
+
See HTML docs at https://areadetector.github.io/areaDetector/ADCore/NDPluginDriver.html
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
nd_array_port: A[SignalRW[str], PvSuffix.rbv("NDArrayPort")]
|
|
56
|
+
enable_callbacks: A[SignalRW[ADCallbacks], PvSuffix.rbv("EnableCallbacks")]
|
|
57
|
+
nd_array_address: A[SignalRW[int], PvSuffix.rbv("NDArrayAddress")]
|
|
58
|
+
array_size0: A[SignalR[int], PvSuffix("ArraySize0_RBV")]
|
|
59
|
+
array_size1: A[SignalR[int], PvSuffix("ArraySize1_RBV")]
|
|
60
|
+
queue_size: A[SignalRW[int], PvSuffix.rbv("QueueSize")]
|
|
59
61
|
|
|
60
62
|
|
|
61
63
|
class NDPluginStatsIO(NDPluginBaseIO):
|
|
62
|
-
"""
|
|
63
|
-
Plugin for computing statistics from an image or region of interest within an image.
|
|
64
|
-
"""
|
|
64
|
+
"""Plugin for computing statistics from an image or ROI within an image.
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
self.compute_statistics = epics_signal_rw(bool, prefix + "ComputeStatistics")
|
|
69
|
-
self.bgd_width = epics_signal_rw(int, prefix + "BgdWidth")
|
|
70
|
-
self.total_array = epics_signal_rw(float, prefix + "TotalArray")
|
|
71
|
-
# Centroid statistics
|
|
72
|
-
self.compute_centroid = epics_signal_rw(bool, prefix + "ComputeCentroid")
|
|
73
|
-
self.centroid_threshold = epics_signal_rw(float, prefix + "CentroidThreshold")
|
|
74
|
-
# X and Y Profiles
|
|
75
|
-
self.compute_profiles = epics_signal_rw(bool, prefix + "ComputeProfiles")
|
|
76
|
-
self.profile_size_x = epics_signal_rw(int, prefix + "ProfileSizeX")
|
|
77
|
-
self.profile_size_y = epics_signal_rw(int, prefix + "ProfileSizeY")
|
|
78
|
-
self.cursor_x = epics_signal_rw(int, prefix + "CursorX")
|
|
79
|
-
self.cursor_y = epics_signal_rw(int, prefix + "CursorY")
|
|
80
|
-
# Array Histogram
|
|
81
|
-
self.compute_histogram = epics_signal_rw(bool, prefix + "ComputeHistogram")
|
|
82
|
-
self.hist_size = epics_signal_rw(int, prefix + "HistSize")
|
|
83
|
-
self.hist_min = epics_signal_rw(float, prefix + "HistMin")
|
|
84
|
-
self.hist_max = epics_signal_rw(float, prefix + "HistMax")
|
|
85
|
-
super().__init__(prefix, name)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class DetectorState(StrictEnum):
|
|
66
|
+
This mirrors the interface provided by ADCore/db/NDStats.template.
|
|
67
|
+
See HTML docs at https://areadetector.github.io/areaDetector/ADCore/NDPluginStats.html
|
|
89
68
|
"""
|
|
90
|
-
|
|
91
|
-
|
|
69
|
+
|
|
70
|
+
# Basic statistics
|
|
71
|
+
compute_statistics: A[SignalRW[bool], PvSuffix.rbv("ComputeStatistics")]
|
|
72
|
+
bgd_width: A[SignalRW[int], PvSuffix.rbv("BgdWidth")]
|
|
73
|
+
total_array: A[SignalRW[float], PvSuffix.rbv("TotalArray")]
|
|
74
|
+
# Centroid statistics
|
|
75
|
+
compute_centroid: A[SignalRW[bool], PvSuffix.rbv("ComputeCentroid")]
|
|
76
|
+
centroid_threshold: A[SignalRW[float], PvSuffix.rbv("CentroidThreshold")]
|
|
77
|
+
# X and Y Profiles
|
|
78
|
+
compute_profiles: A[SignalRW[bool], PvSuffix.rbv("ComputeProfiles")]
|
|
79
|
+
profile_size_x: A[SignalRW[int], PvSuffix.rbv("ProfileSizeX")]
|
|
80
|
+
profile_size_y: A[SignalRW[int], PvSuffix.rbv("ProfileSizeY")]
|
|
81
|
+
cursor_x: A[SignalRW[int], PvSuffix.rbv("CursorX")]
|
|
82
|
+
cursor_y: A[SignalRW[int], PvSuffix.rbv("CursorY")]
|
|
83
|
+
# Array Histogram
|
|
84
|
+
compute_histogram: A[SignalRW[bool], PvSuffix.rbv("ComputeHistogram")]
|
|
85
|
+
hist_size: A[SignalRW[int], PvSuffix.rbv("HistSize")]
|
|
86
|
+
hist_min: A[SignalRW[float], PvSuffix.rbv("HistMin")]
|
|
87
|
+
hist_max: A[SignalRW[float], PvSuffix.rbv("HistMax")]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ADState(StrictEnum):
|
|
91
|
+
"""Default set of states of an AreaDetector driver.
|
|
92
|
+
|
|
93
|
+
See definition in ADApp/ADSrc/ADDriver.h in https://github.com/areaDetector/ADCore.
|
|
92
94
|
"""
|
|
93
95
|
|
|
94
96
|
IDLE = "Idle"
|
|
@@ -105,19 +107,20 @@ class DetectorState(StrictEnum):
|
|
|
105
107
|
|
|
106
108
|
|
|
107
109
|
class ADBaseIO(NDArrayBaseIO):
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
"""Base class from which areaDetector drivers are derived.
|
|
111
|
+
|
|
112
|
+
This mirrors the interface provided by ADCore/db/ADBase.template.
|
|
113
|
+
See HTML docs at https://areadetector.github.io/areaDetector/ADCore/ADDriver.html
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
acquire_time: A[SignalRW[float], PvSuffix.rbv("AcquireTime")]
|
|
117
|
+
acquire_period: A[SignalRW[float], PvSuffix.rbv("AcquirePeriod")]
|
|
118
|
+
num_images: A[SignalRW[int], PvSuffix.rbv("NumImages")]
|
|
119
|
+
image_mode: A[SignalRW[ADImageMode], PvSuffix.rbv("ImageMode")]
|
|
120
|
+
detector_state: A[SignalR[ADState], PvSuffix("DetectorState_RBV")]
|
|
118
121
|
|
|
119
122
|
|
|
120
|
-
class
|
|
123
|
+
class ADCompression(StrictEnum):
|
|
121
124
|
NONE = "None"
|
|
122
125
|
NBIT = "N-bit"
|
|
123
126
|
SZIP = "szip"
|
|
@@ -129,35 +132,57 @@ class Compression(StrictEnum):
|
|
|
129
132
|
|
|
130
133
|
|
|
131
134
|
class NDFileIO(NDPluginBaseIO):
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
135
|
+
"""Base class from which file plugins are derived.
|
|
136
|
+
|
|
137
|
+
This mirrors the interface provided by ADCore/db/NDFile.template.
|
|
138
|
+
See HTML docs at https://areadetector.github.io/areaDetector/ADCore/NDPluginFile.html
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
file_path: A[SignalRW[str], PvSuffix.rbv("FilePath")]
|
|
142
|
+
file_name: A[SignalRW[str], PvSuffix.rbv("FileName")]
|
|
143
|
+
file_path_exists: A[SignalR[bool], PvSuffix("FilePathExists_RBV")]
|
|
144
|
+
file_template: A[SignalRW[str], PvSuffix.rbv("FileTemplate")]
|
|
145
|
+
full_file_name: A[SignalR[str], PvSuffix("FullFileName_RBV")]
|
|
146
|
+
file_number: A[SignalRW[int], PvSuffix("FileNumber")]
|
|
147
|
+
auto_increment: A[SignalRW[bool], PvSuffix("AutoIncrement")]
|
|
148
|
+
file_write_mode: A[SignalRW[ADFileWriteMode], PvSuffix.rbv("FileWriteMode")]
|
|
149
|
+
num_capture: A[SignalRW[int], PvSuffix.rbv("NumCapture")]
|
|
150
|
+
num_captured: A[SignalR[int], PvSuffix("NumCaptured_RBV")]
|
|
151
|
+
capture: A[SignalRW[bool], PvSuffix.rbv("Capture")]
|
|
152
|
+
array_size0: A[SignalR[int], PvSuffix("ArraySize0")]
|
|
153
|
+
array_size1: A[SignalR[int], PvSuffix("ArraySize1")]
|
|
154
|
+
create_directory: A[SignalRW[int], PvSuffix("CreateDirectory")]
|
|
150
155
|
|
|
151
156
|
|
|
152
157
|
class NDFileHDFIO(NDFileIO):
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
158
|
+
"""Plugin for storing data in HDF5 file format.
|
|
159
|
+
|
|
160
|
+
This mirrors the interface provided by ADCore/db/NDFileHDF5.template.
|
|
161
|
+
See HTML docs at https://areadetector.github.io/areaDetector/ADCore/NDFileHDF5.html
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
position_mode: A[SignalRW[bool], PvSuffix.rbv("PositionMode")]
|
|
165
|
+
compression: A[SignalRW[ADCompression], PvSuffix.rbv("Compression")]
|
|
166
|
+
num_extra_dims: A[SignalRW[int], PvSuffix.rbv("NumExtraDims")]
|
|
167
|
+
swmr_mode: A[SignalRW[bool], PvSuffix.rbv("SWMRMode")]
|
|
168
|
+
flush_now: A[SignalRW[bool], PvSuffix("FlushNow")]
|
|
169
|
+
xml_file_name: A[SignalRW[str], PvSuffix.rbv("XMLFileName")]
|
|
170
|
+
num_frames_chunks: A[SignalR[int], PvSuffix("NumFramesChunks_RBV")]
|
|
171
|
+
chunk_size_auto: A[SignalRW[bool], PvSuffix.rbv("ChunkSizeAuto")]
|
|
172
|
+
lazy_open: A[SignalRW[bool], PvSuffix.rbv("LazyOpen")]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class NDCBFlushOnSoftTrgMode(StrictEnum):
|
|
176
|
+
ON_NEW_IMAGE = "OnNewImage"
|
|
177
|
+
IMMEDIATELY = "Immediately"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class NDPluginCBIO(NDPluginBaseIO):
|
|
181
|
+
pre_count: A[SignalRW[int], PvSuffix.rbv("PreCount")]
|
|
182
|
+
post_count: A[SignalRW[int], PvSuffix.rbv("PostCount")]
|
|
183
|
+
preset_trigger_count: A[SignalRW[int], PvSuffix.rbv("PresetTriggerCount")]
|
|
184
|
+
trigger: A[SignalRW[bool], PvSuffix.rbv("Trigger")]
|
|
185
|
+
capture: A[SignalRW[bool], PvSuffix.rbv("Capture")]
|
|
186
|
+
flush_on_soft_trg: A[
|
|
187
|
+
SignalRW[NDCBFlushOnSoftTrgMode], PvSuffix.rbv("FlushOnSoftTrg")
|
|
188
|
+
]
|
|
@@ -10,14 +10,18 @@ from ophyd_async.core import (
|
|
|
10
10
|
set_and_wait_for_value,
|
|
11
11
|
)
|
|
12
12
|
|
|
13
|
-
from ._core_io import
|
|
14
|
-
|
|
13
|
+
from ._core_io import (
|
|
14
|
+
ADBaseIO,
|
|
15
|
+
ADCallbacks,
|
|
16
|
+
ADState,
|
|
17
|
+
NDCBFlushOnSoftTrgMode,
|
|
18
|
+
NDPluginCBIO,
|
|
19
|
+
)
|
|
20
|
+
from ._utils import ADImageMode, stop_busy_record
|
|
15
21
|
|
|
16
22
|
# Default set of states that we should consider "good" i.e. the acquisition
|
|
17
23
|
# is complete and went well
|
|
18
|
-
DEFAULT_GOOD_STATES: frozenset[
|
|
19
|
-
[DetectorState.IDLE, DetectorState.ABORTED]
|
|
20
|
-
)
|
|
24
|
+
DEFAULT_GOOD_STATES: frozenset[ADState] = frozenset([ADState.IDLE, ADState.ABORTED])
|
|
21
25
|
|
|
22
26
|
ADBaseIOT = TypeVar("ADBaseIOT", bound=ADBaseIO)
|
|
23
27
|
ADBaseControllerT = TypeVar("ADBaseControllerT", bound="ADBaseController")
|
|
@@ -27,7 +31,7 @@ class ADBaseController(DetectorController, Generic[ADBaseIOT]):
|
|
|
27
31
|
def __init__(
|
|
28
32
|
self,
|
|
29
33
|
driver: ADBaseIOT,
|
|
30
|
-
good_states: frozenset[
|
|
34
|
+
good_states: frozenset[ADState] = DEFAULT_GOOD_STATES,
|
|
31
35
|
) -> None:
|
|
32
36
|
self.driver = driver
|
|
33
37
|
self.good_states = good_states
|
|
@@ -46,15 +50,16 @@ class ADBaseController(DetectorController, Generic[ADBaseIOT]):
|
|
|
46
50
|
)
|
|
47
51
|
await asyncio.gather(
|
|
48
52
|
self.driver.num_images.set(trigger_info.total_number_of_triggers),
|
|
49
|
-
self.driver.image_mode.set(
|
|
53
|
+
self.driver.image_mode.set(ADImageMode.MULTIPLE),
|
|
50
54
|
)
|
|
51
55
|
|
|
52
56
|
async def arm(self):
|
|
53
57
|
self._arm_status = await self.start_acquiring_driver_and_ensure_status()
|
|
54
58
|
|
|
55
59
|
async def wait_for_idle(self):
|
|
56
|
-
if self._arm_status:
|
|
60
|
+
if self._arm_status and not self._arm_status.done:
|
|
57
61
|
await self._arm_status
|
|
62
|
+
self._arm_status = None
|
|
58
63
|
|
|
59
64
|
async def disarm(self):
|
|
60
65
|
# We can't use caput callback as we already used it in arm() and we can't have
|
|
@@ -66,17 +71,16 @@ class ADBaseController(DetectorController, Generic[ADBaseIOT]):
|
|
|
66
71
|
exposure: float | None = None,
|
|
67
72
|
timeout: float = DEFAULT_TIMEOUT,
|
|
68
73
|
) -> None:
|
|
69
|
-
"""
|
|
70
|
-
|
|
71
|
-
exposure
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
exposure:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
How long to wait for the exposure time and acquire period to be set.
|
|
74
|
+
"""Set the exposure time and acquire period.
|
|
75
|
+
|
|
76
|
+
If exposure is not None, this sets the acquire time to the exposure time
|
|
77
|
+
and sets the acquire period to the exposure time plus the deadtime. This
|
|
78
|
+
is expected behavior for most AreaDetectors, but some may require more
|
|
79
|
+
specialized handling.
|
|
80
|
+
|
|
81
|
+
:param exposure: Desired exposure time, this is a noop if it is None.
|
|
82
|
+
:type exposure: How long to wait for the exposure time and acquire
|
|
83
|
+
period to be set.
|
|
80
84
|
"""
|
|
81
85
|
if exposure is not None:
|
|
82
86
|
full_frame_time = exposure + self.get_deadtime(exposure)
|
|
@@ -86,20 +90,16 @@ class ADBaseController(DetectorController, Generic[ADBaseIOT]):
|
|
|
86
90
|
)
|
|
87
91
|
|
|
88
92
|
async def start_acquiring_driver_and_ensure_status(self) -> AsyncStatus:
|
|
89
|
-
"""
|
|
90
|
-
Start acquiring driver, raising ValueError if the detector is in a bad state.
|
|
93
|
+
"""Start acquiring driver, raising ValueError if the detector is in a bad state.
|
|
91
94
|
|
|
92
95
|
This sets driver.acquire to True, and waits for it to be True up to a timeout.
|
|
93
96
|
Then, it checks that the DetectorState PV is in DEFAULT_GOOD_STATES,
|
|
94
97
|
and otherwise raises a ValueError.
|
|
95
98
|
|
|
96
|
-
|
|
97
|
-
-------
|
|
98
|
-
AsyncStatus:
|
|
99
|
+
:returns AsyncStatus:
|
|
99
100
|
An AsyncStatus that can be awaited to set driver.acquire to True and perform
|
|
100
101
|
subsequent raising (if applicable) due to detector state.
|
|
101
102
|
"""
|
|
102
|
-
|
|
103
103
|
status = await set_and_wait_for_value(
|
|
104
104
|
self.driver.acquire,
|
|
105
105
|
True,
|
|
@@ -108,8 +108,8 @@ class ADBaseController(DetectorController, Generic[ADBaseIOT]):
|
|
|
108
108
|
)
|
|
109
109
|
|
|
110
110
|
async def complete_acquisition() -> None:
|
|
111
|
-
|
|
112
|
-
set_and_wait_for_value and the detector state updating.
|
|
111
|
+
# NOTE: possible race condition here between the callback from
|
|
112
|
+
# set_and_wait_for_value and the detector state updating.
|
|
113
113
|
await status
|
|
114
114
|
state = await self.driver.detector_state.get_value()
|
|
115
115
|
if state not in self.good_states:
|
|
@@ -119,3 +119,82 @@ class ADBaseController(DetectorController, Generic[ADBaseIOT]):
|
|
|
119
119
|
)
|
|
120
120
|
|
|
121
121
|
return AsyncStatus(complete_acquisition())
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ADBaseContAcqController(ADBaseController[ADBaseIO]):
|
|
125
|
+
"""Continuous acquisition interface for an AreaDetector."""
|
|
126
|
+
|
|
127
|
+
def __init__(self, driver: ADBaseIO, cb_plugin: NDPluginCBIO) -> None:
|
|
128
|
+
self.cb_plugin = cb_plugin
|
|
129
|
+
super().__init__(driver)
|
|
130
|
+
|
|
131
|
+
def get_deadtime(self, exposure):
|
|
132
|
+
# For now just set this to something until we can decide how to pass this in
|
|
133
|
+
return 0.001
|
|
134
|
+
|
|
135
|
+
async def ensure_acquisition_settings_valid(
|
|
136
|
+
self, trigger_info: TriggerInfo
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Ensure the trigger mode is valid for the detector."""
|
|
139
|
+
if trigger_info.trigger != DetectorTrigger.INTERNAL:
|
|
140
|
+
# Note that not all detectors will use the `DetectorTrigger` enum
|
|
141
|
+
raise TypeError(
|
|
142
|
+
"The continuous acq interface only supports internal triggering."
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Not all detectors allow for changing exposure times during an acquisition,
|
|
146
|
+
# so in this least-common-denominator implementation check to see if
|
|
147
|
+
# exposure time matches the current exposure time.
|
|
148
|
+
exposure_time = await self.driver.acquire_time.get_value()
|
|
149
|
+
if trigger_info.livetime is not None and trigger_info.livetime != exposure_time:
|
|
150
|
+
raise ValueError(
|
|
151
|
+
f"Detector exposure time currently set to {exposure_time}, "
|
|
152
|
+
f"but requested exposure is {trigger_info.livetime}"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
async def ensure_in_continuous_acquisition_mode(self) -> None:
|
|
156
|
+
"""Ensure the detector is in continuous acquisition mode."""
|
|
157
|
+
image_mode = await self.driver.image_mode.get_value()
|
|
158
|
+
acquiring = await self.driver.acquire.get_value()
|
|
159
|
+
|
|
160
|
+
if image_mode != ADImageMode.CONTINUOUS or not acquiring:
|
|
161
|
+
raise RuntimeError(
|
|
162
|
+
"Driver must be acquiring in continuous mode to use the "
|
|
163
|
+
"cont acq interface"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
async def prepare(self, trigger_info: TriggerInfo) -> None:
|
|
167
|
+
# These are broken out into seperate functions to make it easier to
|
|
168
|
+
# override them in subclasses, for example if you want `prepare` to
|
|
169
|
+
# setup the detector in continuous mode if it isn't already (for now,
|
|
170
|
+
# we assume it already is). If your detector uses different enums
|
|
171
|
+
# for `ImageMode` or `DetectorTrigger`, you should also override these.
|
|
172
|
+
await self.ensure_acquisition_settings_valid(trigger_info)
|
|
173
|
+
await self.ensure_in_continuous_acquisition_mode()
|
|
174
|
+
|
|
175
|
+
# Configure the CB plugin to collect the correct number of triggers
|
|
176
|
+
await asyncio.gather(
|
|
177
|
+
self.cb_plugin.enable_callbacks.set(ADCallbacks.ENABLE),
|
|
178
|
+
self.cb_plugin.pre_count.set(0),
|
|
179
|
+
self.cb_plugin.post_count.set(trigger_info.total_number_of_triggers),
|
|
180
|
+
self.cb_plugin.preset_trigger_count.set(1),
|
|
181
|
+
self.cb_plugin.flush_on_soft_trg.set(NDCBFlushOnSoftTrgMode.ON_NEW_IMAGE),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
async def arm(self) -> None:
|
|
185
|
+
# Start the CB plugin's capture, and cache it in the arm status attr
|
|
186
|
+
self._arm_status = await set_and_wait_for_value(
|
|
187
|
+
self.cb_plugin.capture,
|
|
188
|
+
True,
|
|
189
|
+
timeout=DEFAULT_TIMEOUT,
|
|
190
|
+
wait_for_set_completion=False,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Send the trigger to begin acquisition
|
|
194
|
+
await self.cb_plugin.trigger.set(True, wait=False)
|
|
195
|
+
|
|
196
|
+
async def disarm(self) -> None:
|
|
197
|
+
await stop_busy_record(self.cb_plugin.capture, False, timeout=1)
|
|
198
|
+
if self._arm_status and not self._arm_status.done:
|
|
199
|
+
await self._arm_status
|
|
200
|
+
self._arm_status = None
|
|
@@ -24,18 +24,20 @@ from ophyd_async.core._utils import DEFAULT_TIMEOUT
|
|
|
24
24
|
# from ophyd_async.epics.adcore._core_logic import ADBaseDatasetDescriber
|
|
25
25
|
from ._core_io import (
|
|
26
26
|
ADBaseDatasetDescriber,
|
|
27
|
-
|
|
27
|
+
ADCallbacks,
|
|
28
28
|
NDArrayBaseIO,
|
|
29
29
|
NDFileIO,
|
|
30
30
|
NDPluginBaseIO,
|
|
31
31
|
)
|
|
32
|
-
from ._utils import
|
|
32
|
+
from ._utils import ADFileWriteMode
|
|
33
33
|
|
|
34
34
|
NDFileIOT = TypeVar("NDFileIOT", bound=NDFileIO)
|
|
35
35
|
ADWriterT = TypeVar("ADWriterT", bound="ADWriter")
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
class ADWriter(DetectorWriter, Generic[NDFileIOT]):
|
|
39
|
+
"""Common behavior for all areaDetector writers."""
|
|
40
|
+
|
|
39
41
|
default_suffix: str = "FILE1:"
|
|
40
42
|
|
|
41
43
|
def __init__(
|
|
@@ -92,7 +94,7 @@ class ADWriter(DetectorWriter, Generic[NDFileIOT]):
|
|
|
92
94
|
async def begin_capture(self) -> None:
|
|
93
95
|
info = self._path_provider(device_name=self._name_provider())
|
|
94
96
|
|
|
95
|
-
await self.fileio.enable_callbacks.set(
|
|
97
|
+
await self.fileio.enable_callbacks.set(ADCallbacks.ENABLE)
|
|
96
98
|
|
|
97
99
|
# Set the directory creation depth first, since dir creation callback happens
|
|
98
100
|
# when directory path PV is processed.
|
|
@@ -102,7 +104,7 @@ class ADWriter(DetectorWriter, Generic[NDFileIOT]):
|
|
|
102
104
|
# See https://github.com/bluesky/ophyd-async/issues/122
|
|
103
105
|
self.fileio.file_path.set(str(info.directory_path)),
|
|
104
106
|
self.fileio.file_name.set(info.filename),
|
|
105
|
-
self.fileio.file_write_mode.set(
|
|
107
|
+
self.fileio.file_write_mode.set(ADFileWriteMode.STREAM),
|
|
106
108
|
# For non-HDF file writers, use AD file templating mechanism
|
|
107
109
|
# for generating multi-image datasets
|
|
108
110
|
self.fileio.file_template.set(
|
|
@@ -134,7 +136,7 @@ class ADWriter(DetectorWriter, Generic[NDFileIOT]):
|
|
|
134
136
|
|
|
135
137
|
describe = {
|
|
136
138
|
self._name_provider(): DataKey(
|
|
137
|
-
source=self.
|
|
139
|
+
source=self.fileio.full_file_name.source,
|
|
138
140
|
shape=list(frame_shape),
|
|
139
141
|
dtype="array",
|
|
140
142
|
dtype_numpy=dtype_numpy,
|
|
@@ -144,9 +146,9 @@ class ADWriter(DetectorWriter, Generic[NDFileIOT]):
|
|
|
144
146
|
return describe
|
|
145
147
|
|
|
146
148
|
async def observe_indices_written(
|
|
147
|
-
self, timeout
|
|
149
|
+
self, timeout: float
|
|
148
150
|
) -> AsyncGenerator[int, None]:
|
|
149
|
-
"""Wait until a specific index is ready to be collected"""
|
|
151
|
+
"""Wait until a specific index is ready to be collected."""
|
|
150
152
|
async for num_captured in observe_value(self.fileio.num_captured, timeout):
|
|
151
153
|
yield num_captured // self._multiplier
|
|
152
154
|
|
|
@@ -187,6 +189,7 @@ class ADWriter(DetectorWriter, Generic[NDFileIOT]):
|
|
|
187
189
|
"chunk_shape": (1, *frame_shape),
|
|
188
190
|
# Include file template for reconstruction in consolidator
|
|
189
191
|
"template": file_template,
|
|
192
|
+
"multiplier": self._multiplier,
|
|
190
193
|
},
|
|
191
194
|
uid=None,
|
|
192
195
|
validate=True,
|
|
@@ -210,9 +213,10 @@ class ADWriter(DetectorWriter, Generic[NDFileIOT]):
|
|
|
210
213
|
# Already done a caput callback in _capture_status, so can't do one here
|
|
211
214
|
await self.fileio.capture.set(False, wait=False)
|
|
212
215
|
await wait_for_value(self.fileio.capture, False, DEFAULT_TIMEOUT)
|
|
213
|
-
if self._capture_status:
|
|
216
|
+
if self._capture_status and not self._capture_status.done:
|
|
214
217
|
# We kicked off an open, so wait for it to return
|
|
215
218
|
await self._capture_status
|
|
219
|
+
self._capture_status = None
|
|
216
220
|
|
|
217
221
|
@property
|
|
218
222
|
def hints(self) -> Hints:
|