ophyd-async 0.8.0a5__py3-none-any.whl → 0.9.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/_version.py +2 -2
- ophyd_async/core/__init__.py +4 -26
- ophyd_async/core/_detector.py +9 -9
- ophyd_async/core/_device.py +27 -8
- ophyd_async/core/_protocol.py +0 -28
- ophyd_async/core/_signal.py +111 -136
- ophyd_async/core/_table.py +9 -4
- ophyd_async/core/_utils.py +11 -2
- ophyd_async/epics/adaravis/_aravis_controller.py +8 -8
- ophyd_async/epics/adaravis/_aravis_io.py +4 -4
- ophyd_async/epics/adcore/_core_io.py +21 -21
- ophyd_async/epics/adcore/_core_logic.py +6 -3
- ophyd_async/epics/adcore/_hdf_writer.py +6 -3
- ophyd_async/epics/adcore/_single_trigger.py +1 -1
- ophyd_async/epics/adcore/_utils.py +35 -35
- ophyd_async/epics/adkinetix/_kinetix_controller.py +7 -7
- ophyd_async/epics/adkinetix/_kinetix_io.py +7 -7
- ophyd_async/epics/adpilatus/_pilatus.py +3 -3
- ophyd_async/epics/adpilatus/_pilatus_controller.py +4 -4
- ophyd_async/epics/adpilatus/_pilatus_io.py +5 -5
- ophyd_async/epics/adsimdetector/_sim_controller.py +2 -2
- ophyd_async/epics/advimba/_vimba_controller.py +14 -14
- ophyd_async/epics/advimba/_vimba_io.py +23 -23
- ophyd_async/epics/core/_p4p.py +19 -0
- ophyd_async/epics/core/_pvi_connector.py +4 -2
- ophyd_async/epics/core/_signal.py +9 -2
- ophyd_async/epics/core/_util.py +9 -0
- ophyd_async/epics/demo/_mover.py +2 -2
- ophyd_async/epics/demo/_sensor.py +2 -2
- ophyd_async/epics/eiger/_eiger_controller.py +10 -5
- ophyd_async/epics/eiger/_eiger_io.py +3 -3
- ophyd_async/epics/motor.py +8 -5
- ophyd_async/epics/testing/__init__.py +24 -0
- ophyd_async/epics/testing/_example_ioc.py +107 -0
- ophyd_async/epics/testing/_utils.py +78 -0
- ophyd_async/epics/testing/test_records.db +158 -0
- ophyd_async/epics/testing/test_records_pva.db +177 -0
- ophyd_async/fastcs/core.py +2 -2
- ophyd_async/fastcs/panda/_block.py +9 -9
- ophyd_async/fastcs/panda/_control.py +2 -2
- ophyd_async/fastcs/panda/_hdf_panda.py +4 -1
- ophyd_async/fastcs/panda/_trigger.py +7 -7
- ophyd_async/plan_stubs/_fly.py +1 -1
- ophyd_async/sim/demo/_sim_motor.py +34 -32
- ophyd_async/tango/__init__.py +0 -43
- ophyd_async/tango/{signal → core}/__init__.py +7 -2
- ophyd_async/tango/{base_devices → core}/_base_device.py +38 -64
- ophyd_async/tango/{signal → core}/_signal.py +13 -3
- ophyd_async/tango/{base_devices → core}/_tango_readable.py +3 -4
- ophyd_async/tango/{signal → core}/_tango_transport.py +1 -1
- ophyd_async/tango/demo/_counter.py +6 -7
- ophyd_async/tango/demo/_mover.py +8 -7
- ophyd_async/testing/__init__.py +33 -0
- ophyd_async/testing/_assert.py +128 -0
- ophyd_async/{core → testing}/_mock_signal_utils.py +12 -8
- ophyd_async/testing/_wait_for_pending.py +22 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/METADATA +49 -47
- ophyd_async-0.9.0a1.dist-info/RECORD +119 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/WHEEL +1 -1
- ophyd_async/tango/base_devices/__init__.py +0 -4
- ophyd_async-0.8.0a5.dist-info/RECORD +0 -112
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/LICENSE +0 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0a1.dist-info}/top_level.txt +0 -0
ophyd_async/core/_table.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections.abc import Sequence
|
|
3
|
+
from collections.abc import Callable, Sequence
|
|
4
4
|
from typing import Annotated, Any, TypeVar, get_origin
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
@@ -19,6 +19,13 @@ def _concat(value1, value2):
|
|
|
19
19
|
return value1 + value2
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
def _make_default_factory(dtype: np.dtype) -> Callable[[], np.ndarray]:
|
|
23
|
+
def numpy_array_default_factory() -> np.ndarray:
|
|
24
|
+
return np.array([], dtype)
|
|
25
|
+
|
|
26
|
+
return numpy_array_default_factory
|
|
27
|
+
|
|
28
|
+
|
|
22
29
|
class Table(BaseModel):
|
|
23
30
|
"""An abstraction of a Table of str to numpy array."""
|
|
24
31
|
|
|
@@ -45,9 +52,7 @@ class Table(BaseModel):
|
|
|
45
52
|
NpArrayPydanticAnnotation.factory(
|
|
46
53
|
data_type=dtype.type, dimensions=1, strict_data_typing=False
|
|
47
54
|
),
|
|
48
|
-
Field(
|
|
49
|
-
default_factory=lambda dtype=dtype: np.array([], dtype=dtype)
|
|
50
|
-
),
|
|
55
|
+
Field(default_factory=_make_default_factory(dtype)),
|
|
51
56
|
]
|
|
52
57
|
elif get_origin(anno) is Sequence:
|
|
53
58
|
new_anno = Annotated[anno, Field(default_factory=list)]
|
ophyd_async/core/_utils.py
CHANGED
|
@@ -17,11 +17,20 @@ DEFAULT_TIMEOUT = 10.0
|
|
|
17
17
|
ErrorText = str | Mapping[str, Exception]
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
class
|
|
20
|
+
class StrictEnumMeta(EnumMeta):
|
|
21
|
+
def __new__(metacls, *args, **kwargs):
|
|
22
|
+
ret = super().__new__(metacls, *args, **kwargs)
|
|
23
|
+
lowercase_names = [x.name for x in ret if not x.name.isupper()] # type: ignore
|
|
24
|
+
if lowercase_names:
|
|
25
|
+
raise TypeError(f"Names {lowercase_names} should be uppercase")
|
|
26
|
+
return ret
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StrictEnum(str, Enum, metaclass=StrictEnumMeta):
|
|
21
30
|
"""All members should exist in the Backend, and there will be no extras"""
|
|
22
31
|
|
|
23
32
|
|
|
24
|
-
class SubsetEnumMeta(
|
|
33
|
+
class SubsetEnumMeta(StrictEnumMeta):
|
|
25
34
|
def __call__(self, value, *args, **kwargs): # type: ignore
|
|
26
35
|
if isinstance(value, str) and not isinstance(value, self):
|
|
27
36
|
return value
|
|
@@ -31,9 +31,9 @@ class AravisController(DetectorController):
|
|
|
31
31
|
|
|
32
32
|
async def prepare(self, trigger_info: TriggerInfo):
|
|
33
33
|
if trigger_info.total_number_of_triggers == 0:
|
|
34
|
-
image_mode = adcore.ImageMode.
|
|
34
|
+
image_mode = adcore.ImageMode.CONTINUOUS
|
|
35
35
|
else:
|
|
36
|
-
image_mode = adcore.ImageMode.
|
|
36
|
+
image_mode = adcore.ImageMode.MULTIPLE
|
|
37
37
|
if (exposure := trigger_info.livetime) is not None:
|
|
38
38
|
await self._drv.acquire_time.set(exposure)
|
|
39
39
|
|
|
@@ -58,9 +58,9 @@ class AravisController(DetectorController):
|
|
|
58
58
|
self, trigger: DetectorTrigger
|
|
59
59
|
) -> tuple[AravisTriggerMode, AravisTriggerSource]:
|
|
60
60
|
supported_trigger_types = (
|
|
61
|
-
DetectorTrigger.
|
|
62
|
-
DetectorTrigger.
|
|
63
|
-
DetectorTrigger.
|
|
61
|
+
DetectorTrigger.CONSTANT_GATE,
|
|
62
|
+
DetectorTrigger.EDGE_TRIGGER,
|
|
63
|
+
DetectorTrigger.INTERNAL,
|
|
64
64
|
)
|
|
65
65
|
if trigger not in supported_trigger_types:
|
|
66
66
|
raise ValueError(
|
|
@@ -68,10 +68,10 @@ class AravisController(DetectorController):
|
|
|
68
68
|
f"types: {supported_trigger_types} but was asked to "
|
|
69
69
|
f"use {trigger}"
|
|
70
70
|
)
|
|
71
|
-
if trigger == DetectorTrigger.
|
|
72
|
-
return AravisTriggerMode.
|
|
71
|
+
if trigger == DetectorTrigger.INTERNAL:
|
|
72
|
+
return AravisTriggerMode.OFF, AravisTriggerSource.FREERUN
|
|
73
73
|
else:
|
|
74
|
-
return (AravisTriggerMode.
|
|
74
|
+
return (AravisTriggerMode.ON, f"Line{self.gpio_number}") # type: ignore
|
|
75
75
|
|
|
76
76
|
async def disarm(self):
|
|
77
77
|
await adcore.stop_busy_record(self._drv.acquire, False, timeout=1)
|
|
@@ -6,8 +6,8 @@ from ophyd_async.epics.core import epics_signal_rw_rbv
|
|
|
6
6
|
class AravisTriggerMode(StrictEnum):
|
|
7
7
|
"""GigEVision GenICAM standard: on=externally triggered"""
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
ON = "On"
|
|
10
|
+
OFF = "Off"
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
"""A minimal set of TriggerSources that must be supported by the underlying record.
|
|
@@ -20,8 +20,8 @@ class AravisTriggerMode(StrictEnum):
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class AravisTriggerSource(SubsetEnum):
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
FREERUN = "Freerun"
|
|
24
|
+
LINE1 = "Line1"
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class AravisDriverIO(adcore.ADBaseIO):
|
|
@@ -9,8 +9,8 @@ from ._utils import ADBaseDataType, FileWriteMode, ImageMode
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Callback(StrictEnum):
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
ENABLE = "Enable"
|
|
13
|
+
DISABLE = "Disable"
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class NDArrayBaseIO(Device):
|
|
@@ -72,17 +72,17 @@ class DetectorState(StrictEnum):
|
|
|
72
72
|
See definition in ADApp/ADSrc/ADDriver.h in https://github.com/areaDetector/ADCore
|
|
73
73
|
"""
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
75
|
+
IDLE = "Idle"
|
|
76
|
+
ACQUIRE = "Acquire"
|
|
77
|
+
READOUT = "Readout"
|
|
78
|
+
CORRECT = "Correct"
|
|
79
|
+
SAVING = "Saving"
|
|
80
|
+
ABORTING = "Aborting"
|
|
81
|
+
ERROR = "Error"
|
|
82
|
+
WAITING = "Waiting"
|
|
83
|
+
INITIALIZING = "Initializing"
|
|
84
|
+
DISCONNECTED = "Disconnected"
|
|
85
|
+
ABORTED = "Aborted"
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
class ADBaseIO(NDArrayBaseIO):
|
|
@@ -99,14 +99,14 @@ class ADBaseIO(NDArrayBaseIO):
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
class Compression(StrictEnum):
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
NONE = "None"
|
|
103
|
+
NBIT = "N-bit"
|
|
104
|
+
SZIP = "szip"
|
|
105
|
+
ZLIB = "zlib"
|
|
106
|
+
BLOSC = "Blosc"
|
|
107
|
+
BSLZ4 = "BSLZ4"
|
|
108
|
+
LZ4 = "LZ4"
|
|
109
|
+
JPEG = "JPEG"
|
|
110
110
|
|
|
111
111
|
|
|
112
112
|
class NDFileHDFIO(NDPluginBaseIO):
|
|
@@ -14,7 +14,7 @@ from ._core_io import ADBaseIO, DetectorState
|
|
|
14
14
|
# Default set of states that we should consider "good" i.e. the acquisition
|
|
15
15
|
# is complete and went well
|
|
16
16
|
DEFAULT_GOOD_STATES: frozenset[DetectorState] = frozenset(
|
|
17
|
-
[DetectorState.
|
|
17
|
+
[DetectorState.IDLE, DetectorState.ABORTED]
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
|
|
@@ -91,7 +91,9 @@ async def start_acquiring_driver_and_ensure_status(
|
|
|
91
91
|
subsequent raising (if applicable) due to detector state.
|
|
92
92
|
"""
|
|
93
93
|
|
|
94
|
-
status = await set_and_wait_for_value(
|
|
94
|
+
status = await set_and_wait_for_value(
|
|
95
|
+
driver.acquire, True, timeout=timeout, wait_for_set_completion=False
|
|
96
|
+
)
|
|
95
97
|
|
|
96
98
|
async def complete_acquisition() -> None:
|
|
97
99
|
"""NOTE: possible race condition here between the callback from
|
|
@@ -100,7 +102,8 @@ async def start_acquiring_driver_and_ensure_status(
|
|
|
100
102
|
state = await driver.detector_state.get_value()
|
|
101
103
|
if state not in good_states:
|
|
102
104
|
raise ValueError(
|
|
103
|
-
f"Final detector state {state} not in valid end
|
|
105
|
+
f"Final detector state {state.value} not in valid end "
|
|
106
|
+
f"states: {good_states}"
|
|
104
107
|
)
|
|
105
108
|
|
|
106
109
|
return AsyncStatus(complete_acquisition())
|
|
@@ -20,7 +20,7 @@ from ophyd_async.core import (
|
|
|
20
20
|
wait_for_value,
|
|
21
21
|
)
|
|
22
22
|
|
|
23
|
-
from ._core_io import NDArrayBaseIO, NDFileHDFIO
|
|
23
|
+
from ._core_io import Callback, NDArrayBaseIO, NDFileHDFIO
|
|
24
24
|
from ._utils import (
|
|
25
25
|
FileWriteMode,
|
|
26
26
|
convert_param_dtype_to_np,
|
|
@@ -67,10 +67,11 @@ class ADHDFWriter(DetectorWriter):
|
|
|
67
67
|
self.hdf.file_path.set(str(info.directory_path)),
|
|
68
68
|
self.hdf.file_name.set(info.filename),
|
|
69
69
|
self.hdf.file_template.set("%s/%s.h5"),
|
|
70
|
-
self.hdf.file_write_mode.set(FileWriteMode.
|
|
70
|
+
self.hdf.file_write_mode.set(FileWriteMode.STREAM),
|
|
71
71
|
# Never use custom xml layout file but use the one defined
|
|
72
72
|
# in the source code file NDFileHDF5LayoutXML.cpp
|
|
73
73
|
self.hdf.xml_file_name.set(""),
|
|
74
|
+
self.hdf.enable_callbacks.set(Callback.ENABLE),
|
|
74
75
|
)
|
|
75
76
|
|
|
76
77
|
assert (
|
|
@@ -80,7 +81,9 @@ class ADHDFWriter(DetectorWriter):
|
|
|
80
81
|
# Overwrite num_capture to go forever
|
|
81
82
|
await self.hdf.num_capture.set(0)
|
|
82
83
|
# Wait for it to start, stashing the status that tells us when it finishes
|
|
83
|
-
self._capture_status = await set_and_wait_for_value(
|
|
84
|
+
self._capture_status = await set_and_wait_for_value(
|
|
85
|
+
self.hdf.capture, True, wait_for_set_completion=False
|
|
86
|
+
)
|
|
84
87
|
name = self._name_provider()
|
|
85
88
|
detector_shape = await self._dataset_describer.shape()
|
|
86
89
|
np_dtype = await self._dataset_describer.np_datatype()
|
|
@@ -34,7 +34,7 @@ class SingleTriggerDetector(StandardReadable, Triggerable):
|
|
|
34
34
|
@AsyncStatus.wrap
|
|
35
35
|
async def stage(self) -> None:
|
|
36
36
|
await asyncio.gather(
|
|
37
|
-
self.drv.image_mode.set(ImageMode.
|
|
37
|
+
self.drv.image_mode.set(ImageMode.SINGLE),
|
|
38
38
|
self.drv.wait_for_plugins.set(True),
|
|
39
39
|
)
|
|
40
40
|
await super().stage()
|
|
@@ -11,42 +11,42 @@ from ophyd_async.core import (
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class ADBaseDataType(StrictEnum):
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
INT8 = "Int8"
|
|
15
|
+
UINT8 = "UInt8"
|
|
16
|
+
INT16 = "Int16"
|
|
17
|
+
UINT16 = "UInt16"
|
|
18
|
+
INT32 = "Int32"
|
|
19
|
+
UINT32 = "UInt32"
|
|
20
|
+
INT64 = "Int64"
|
|
21
|
+
UINT64 = "UInt64"
|
|
22
|
+
FLOAT32 = "Float32"
|
|
23
|
+
FLOAT64 = "Float64"
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def convert_ad_dtype_to_np(ad_dtype: ADBaseDataType) -> str:
|
|
27
27
|
ad_dtype_to_np_dtype = {
|
|
28
|
-
ADBaseDataType.
|
|
29
|
-
ADBaseDataType.
|
|
30
|
-
ADBaseDataType.
|
|
31
|
-
ADBaseDataType.
|
|
32
|
-
ADBaseDataType.
|
|
33
|
-
ADBaseDataType.
|
|
34
|
-
ADBaseDataType.
|
|
35
|
-
ADBaseDataType.
|
|
36
|
-
ADBaseDataType.
|
|
37
|
-
ADBaseDataType.
|
|
28
|
+
ADBaseDataType.INT8: "|i1",
|
|
29
|
+
ADBaseDataType.UINT8: "|u1",
|
|
30
|
+
ADBaseDataType.INT16: "<i2",
|
|
31
|
+
ADBaseDataType.UINT16: "<u2",
|
|
32
|
+
ADBaseDataType.INT32: "<i4",
|
|
33
|
+
ADBaseDataType.UINT32: "<u4",
|
|
34
|
+
ADBaseDataType.INT64: "<i8",
|
|
35
|
+
ADBaseDataType.UINT64: "<u8",
|
|
36
|
+
ADBaseDataType.FLOAT32: "<f4",
|
|
37
|
+
ADBaseDataType.FLOAT64: "<f8",
|
|
38
38
|
}
|
|
39
39
|
return ad_dtype_to_np_dtype[ad_dtype]
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def convert_pv_dtype_to_np(datatype: str) -> str:
|
|
43
43
|
_pvattribute_to_ad_datatype = {
|
|
44
|
-
"DBR_SHORT": ADBaseDataType.
|
|
45
|
-
"DBR_ENUM": ADBaseDataType.
|
|
46
|
-
"DBR_INT": ADBaseDataType.
|
|
47
|
-
"DBR_LONG": ADBaseDataType.
|
|
48
|
-
"DBR_FLOAT": ADBaseDataType.
|
|
49
|
-
"DBR_DOUBLE": ADBaseDataType.
|
|
44
|
+
"DBR_SHORT": ADBaseDataType.INT16,
|
|
45
|
+
"DBR_ENUM": ADBaseDataType.INT16,
|
|
46
|
+
"DBR_INT": ADBaseDataType.INT32,
|
|
47
|
+
"DBR_LONG": ADBaseDataType.INT32,
|
|
48
|
+
"DBR_FLOAT": ADBaseDataType.FLOAT32,
|
|
49
|
+
"DBR_DOUBLE": ADBaseDataType.FLOAT64,
|
|
50
50
|
}
|
|
51
51
|
if datatype in ["DBR_STRING", "DBR_CHAR"]:
|
|
52
52
|
np_datatype = "s40"
|
|
@@ -62,9 +62,9 @@ def convert_pv_dtype_to_np(datatype: str) -> str:
|
|
|
62
62
|
|
|
63
63
|
def convert_param_dtype_to_np(datatype: str) -> str:
|
|
64
64
|
_paramattribute_to_ad_datatype = {
|
|
65
|
-
"INT": ADBaseDataType.
|
|
66
|
-
"INT64": ADBaseDataType.
|
|
67
|
-
"DOUBLE": ADBaseDataType.
|
|
65
|
+
"INT": ADBaseDataType.INT32,
|
|
66
|
+
"INT64": ADBaseDataType.INT64,
|
|
67
|
+
"DOUBLE": ADBaseDataType.FLOAT64,
|
|
68
68
|
}
|
|
69
69
|
if datatype in ["STRING"]:
|
|
70
70
|
np_datatype = "s40"
|
|
@@ -79,15 +79,15 @@ def convert_param_dtype_to_np(datatype: str) -> str:
|
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
class FileWriteMode(StrictEnum):
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
SINGLE = "Single"
|
|
83
|
+
CAPTURE = "Capture"
|
|
84
|
+
STREAM = "Stream"
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
class ImageMode(StrictEnum):
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
SINGLE = "Single"
|
|
89
|
+
MULTIPLE = "Multiple"
|
|
90
|
+
CONTINUOUS = "Continuous"
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
class NDAttributeDataType(StrictEnum):
|
|
@@ -11,10 +11,10 @@ from ophyd_async.epics import adcore
|
|
|
11
11
|
from ._kinetix_io import KinetixDriverIO, KinetixTriggerMode
|
|
12
12
|
|
|
13
13
|
KINETIX_TRIGGER_MODE_MAP = {
|
|
14
|
-
DetectorTrigger.
|
|
15
|
-
DetectorTrigger.
|
|
16
|
-
DetectorTrigger.
|
|
17
|
-
DetectorTrigger.
|
|
14
|
+
DetectorTrigger.INTERNAL: KinetixTriggerMode.INTERNAL,
|
|
15
|
+
DetectorTrigger.CONSTANT_GATE: KinetixTriggerMode.GATE,
|
|
16
|
+
DetectorTrigger.VARIABLE_GATE: KinetixTriggerMode.GATE,
|
|
17
|
+
DetectorTrigger.EDGE_TRIGGER: KinetixTriggerMode.EDGE,
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
|
|
@@ -33,11 +33,11 @@ class KinetixController(DetectorController):
|
|
|
33
33
|
await asyncio.gather(
|
|
34
34
|
self._drv.trigger_mode.set(KINETIX_TRIGGER_MODE_MAP[trigger_info.trigger]),
|
|
35
35
|
self._drv.num_images.set(trigger_info.total_number_of_triggers),
|
|
36
|
-
self._drv.image_mode.set(adcore.ImageMode.
|
|
36
|
+
self._drv.image_mode.set(adcore.ImageMode.MULTIPLE),
|
|
37
37
|
)
|
|
38
38
|
if trigger_info.livetime is not None and trigger_info.trigger not in [
|
|
39
|
-
DetectorTrigger.
|
|
40
|
-
DetectorTrigger.
|
|
39
|
+
DetectorTrigger.VARIABLE_GATE,
|
|
40
|
+
DetectorTrigger.CONSTANT_GATE,
|
|
41
41
|
]:
|
|
42
42
|
await self._drv.acquire_time.set(trigger_info.livetime)
|
|
43
43
|
|
|
@@ -4,16 +4,16 @@ from ophyd_async.epics.core import epics_signal_rw_rbv
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class KinetixTriggerMode(StrictEnum):
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
INTERNAL = "Internal"
|
|
8
|
+
EDGE = "Rising Edge"
|
|
9
|
+
GATE = "Exp. Gate"
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class KinetixReadoutMode(StrictEnum):
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
SENSITIVITY = 1
|
|
14
|
+
SPEED = 2
|
|
15
|
+
DYNAMIC_RANGE = 3
|
|
16
|
+
SUB_ELECTRON = 4
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class KinetixDriverIO(adcore.ADBaseIO):
|
|
@@ -17,10 +17,10 @@ class PilatusReadoutTime(float, Enum):
|
|
|
17
17
|
"""Pilatus readout time per model in ms"""
|
|
18
18
|
|
|
19
19
|
# Cite: https://media.dectris.com/User_Manual-PILATUS2-V1_4.pdf
|
|
20
|
-
|
|
20
|
+
PILATUS2 = 2.28e-3
|
|
21
21
|
|
|
22
22
|
# Cite: https://media.dectris.com/user-manual-pilatus3-2020.pdf
|
|
23
|
-
|
|
23
|
+
PILATUS3 = 0.95e-3
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class PilatusDetector(StandardDetector):
|
|
@@ -33,7 +33,7 @@ class PilatusDetector(StandardDetector):
|
|
|
33
33
|
self,
|
|
34
34
|
prefix: str,
|
|
35
35
|
path_provider: PathProvider,
|
|
36
|
-
readout_time: PilatusReadoutTime = PilatusReadoutTime.
|
|
36
|
+
readout_time: PilatusReadoutTime = PilatusReadoutTime.PILATUS3,
|
|
37
37
|
drv_suffix: str = "cam1:",
|
|
38
38
|
hdf_suffix: str = "HDF1:",
|
|
39
39
|
name: str = "",
|
|
@@ -15,9 +15,9 @@ from ._pilatus_io import PilatusDriverIO, PilatusTriggerMode
|
|
|
15
15
|
|
|
16
16
|
class PilatusController(DetectorController):
|
|
17
17
|
_supported_trigger_types = {
|
|
18
|
-
DetectorTrigger.
|
|
19
|
-
DetectorTrigger.
|
|
20
|
-
DetectorTrigger.
|
|
18
|
+
DetectorTrigger.INTERNAL: PilatusTriggerMode.INTERNAL,
|
|
19
|
+
DetectorTrigger.CONSTANT_GATE: PilatusTriggerMode.EXT_ENABLE,
|
|
20
|
+
DetectorTrigger.VARIABLE_GATE: PilatusTriggerMode.EXT_ENABLE,
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
def __init__(
|
|
@@ -44,7 +44,7 @@ class PilatusController(DetectorController):
|
|
|
44
44
|
if trigger_info.total_number_of_triggers == 0
|
|
45
45
|
else trigger_info.total_number_of_triggers
|
|
46
46
|
),
|
|
47
|
-
self._drv.image_mode.set(adcore.ImageMode.
|
|
47
|
+
self._drv.image_mode.set(adcore.ImageMode.MULTIPLE),
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
async def arm(self):
|
|
@@ -4,11 +4,11 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw_rbv
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class PilatusTriggerMode(StrictEnum):
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
INTERNAL = "Internal"
|
|
8
|
+
EXT_ENABLE = "Ext. Enable"
|
|
9
|
+
EXT_TRIGGER = "Ext. Trigger"
|
|
10
|
+
MULT_TRIGGER = "Mult. Trigger"
|
|
11
|
+
ALIGNMENT = "Alignment"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class PilatusDriverIO(adcore.ADBaseIO):
|
|
@@ -26,14 +26,14 @@ class SimController(DetectorController):
|
|
|
26
26
|
|
|
27
27
|
async def prepare(self, trigger_info: TriggerInfo):
|
|
28
28
|
assert (
|
|
29
|
-
trigger_info.trigger == DetectorTrigger.
|
|
29
|
+
trigger_info.trigger == DetectorTrigger.INTERNAL
|
|
30
30
|
), "fly scanning (i.e. external triggering) is not supported for this device"
|
|
31
31
|
self.frame_timeout = (
|
|
32
32
|
DEFAULT_TIMEOUT + await self.driver.acquire_time.get_value()
|
|
33
33
|
)
|
|
34
34
|
await asyncio.gather(
|
|
35
35
|
self.driver.num_images.set(trigger_info.total_number_of_triggers),
|
|
36
|
-
self.driver.image_mode.set(adcore.ImageMode.
|
|
36
|
+
self.driver.image_mode.set(adcore.ImageMode.MULTIPLE),
|
|
37
37
|
)
|
|
38
38
|
|
|
39
39
|
async def arm(self):
|
|
@@ -11,17 +11,17 @@ from ophyd_async.epics import adcore
|
|
|
11
11
|
from ._vimba_io import VimbaDriverIO, VimbaExposeOutMode, VimbaOnOff, VimbaTriggerSource
|
|
12
12
|
|
|
13
13
|
TRIGGER_MODE = {
|
|
14
|
-
DetectorTrigger.
|
|
15
|
-
DetectorTrigger.
|
|
16
|
-
DetectorTrigger.
|
|
17
|
-
DetectorTrigger.
|
|
14
|
+
DetectorTrigger.INTERNAL: VimbaOnOff.OFF,
|
|
15
|
+
DetectorTrigger.CONSTANT_GATE: VimbaOnOff.ON,
|
|
16
|
+
DetectorTrigger.VARIABLE_GATE: VimbaOnOff.ON,
|
|
17
|
+
DetectorTrigger.EDGE_TRIGGER: VimbaOnOff.ON,
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
EXPOSE_OUT_MODE = {
|
|
21
|
-
DetectorTrigger.
|
|
22
|
-
DetectorTrigger.
|
|
23
|
-
DetectorTrigger.
|
|
24
|
-
DetectorTrigger.
|
|
21
|
+
DetectorTrigger.INTERNAL: VimbaExposeOutMode.TIMED,
|
|
22
|
+
DetectorTrigger.CONSTANT_GATE: VimbaExposeOutMode.TRIGGER_WIDTH,
|
|
23
|
+
DetectorTrigger.VARIABLE_GATE: VimbaExposeOutMode.TRIGGER_WIDTH,
|
|
24
|
+
DetectorTrigger.EDGE_TRIGGER: VimbaExposeOutMode.TIMED,
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
|
|
@@ -41,17 +41,17 @@ class VimbaController(DetectorController):
|
|
|
41
41
|
self._drv.trigger_mode.set(TRIGGER_MODE[trigger_info.trigger]),
|
|
42
42
|
self._drv.exposure_mode.set(EXPOSE_OUT_MODE[trigger_info.trigger]),
|
|
43
43
|
self._drv.num_images.set(trigger_info.total_number_of_triggers),
|
|
44
|
-
self._drv.image_mode.set(adcore.ImageMode.
|
|
44
|
+
self._drv.image_mode.set(adcore.ImageMode.MULTIPLE),
|
|
45
45
|
)
|
|
46
46
|
if trigger_info.livetime is not None and trigger_info.trigger not in [
|
|
47
|
-
DetectorTrigger.
|
|
48
|
-
DetectorTrigger.
|
|
47
|
+
DetectorTrigger.VARIABLE_GATE,
|
|
48
|
+
DetectorTrigger.CONSTANT_GATE,
|
|
49
49
|
]:
|
|
50
50
|
await self._drv.acquire_time.set(trigger_info.livetime)
|
|
51
|
-
if trigger_info.trigger != DetectorTrigger.
|
|
52
|
-
self._drv.trigger_source.set(VimbaTriggerSource.
|
|
51
|
+
if trigger_info.trigger != DetectorTrigger.INTERNAL:
|
|
52
|
+
self._drv.trigger_source.set(VimbaTriggerSource.LINE1)
|
|
53
53
|
else:
|
|
54
|
-
self._drv.trigger_source.set(VimbaTriggerSource.
|
|
54
|
+
self._drv.trigger_source.set(VimbaTriggerSource.FREERUN)
|
|
55
55
|
|
|
56
56
|
async def arm(self):
|
|
57
57
|
self._arm_status = await adcore.start_acquiring_driver_and_ensure_status(
|
|
@@ -4,44 +4,44 @@ from ophyd_async.epics.core import epics_signal_rw_rbv
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class VimbaPixelFormat(StrictEnum):
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
INTERNAL = "Mono8"
|
|
8
|
+
EXT_ENABLE = "Mono12"
|
|
9
|
+
EXT_TRIGGER = "Ext. Trigger"
|
|
10
|
+
MULT_TRIGGER = "Mult. Trigger"
|
|
11
|
+
ALIGNMENT = "Alignment"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class VimbaConvertFormat(StrictEnum):
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
NONE = "None"
|
|
16
|
+
MONO8 = "Mono8"
|
|
17
|
+
MONO16 = "Mono16"
|
|
18
|
+
RGB8 = "RGB8"
|
|
19
|
+
RGB16 = "RGB16"
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class VimbaTriggerSource(StrictEnum):
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
FREERUN = "Freerun"
|
|
24
|
+
LINE1 = "Line1"
|
|
25
|
+
LINE2 = "Line2"
|
|
26
|
+
FIXED_RATE = "FixedRate"
|
|
27
|
+
SOFTWARE = "Software"
|
|
28
|
+
ACTION0 = "Action0"
|
|
29
|
+
ACTION1 = "Action1"
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class VimbaOverlap(StrictEnum):
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
OFF = "Off"
|
|
34
|
+
PREV_FRAME = "PreviousFrame"
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class VimbaOnOff(StrictEnum):
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
ON = "On"
|
|
39
|
+
OFF = "Off"
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
class VimbaExposeOutMode(StrictEnum):
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
TIMED = "Timed" # Use ExposureTime PV
|
|
44
|
+
TRIGGER_WIDTH = "TriggerWidth" # Expose for length of high signal
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
class VimbaDriverIO(adcore.ADBaseIO):
|
ophyd_async/epics/core/_p4p.py
CHANGED
|
@@ -94,6 +94,22 @@ class PvaConverter(Generic[SignalDatatypeT]):
|
|
|
94
94
|
return value
|
|
95
95
|
|
|
96
96
|
|
|
97
|
+
class PvaLongStringConverter(PvaConverter[str]):
|
|
98
|
+
def __init__(self):
|
|
99
|
+
super().__init__(str)
|
|
100
|
+
|
|
101
|
+
def value(self, value: Any) -> Any:
|
|
102
|
+
# Value here is a null terminated array of ascii codes.
|
|
103
|
+
# We strip out the null terminator, and convert each code
|
|
104
|
+
# to the corresponding char, joining into a string
|
|
105
|
+
return value["value"].tobytes().rstrip(b"\0").decode()
|
|
106
|
+
|
|
107
|
+
def write_value(self, value: Any) -> Any:
|
|
108
|
+
# Inverse of reading - convert each character into it's ascii code,
|
|
109
|
+
# put into a list, and add null terminator.
|
|
110
|
+
return np.frombuffer(str(value).encode() + b"\0", dtype=np.int8)
|
|
111
|
+
|
|
112
|
+
|
|
97
113
|
class DisconnectedPvaConverter(PvaConverter):
|
|
98
114
|
def __getattribute__(self, __name: str) -> Any:
|
|
99
115
|
raise NotImplementedError("No PV has been set as connect() has not been called")
|
|
@@ -252,6 +268,9 @@ def make_converter(datatype: type | None, values: dict[str, Any]) -> PvaConverte
|
|
|
252
268
|
elif datatype in (None, inferred_datatype):
|
|
253
269
|
# If datatype matches what we are given then allow it and use inferred converter
|
|
254
270
|
return converter_cls(inferred_datatype)
|
|
271
|
+
# Allow waveforms with FTVL=CHAR to be treated as str when requested
|
|
272
|
+
elif datatype is str and inferred_datatype == Array1D[np.int8]:
|
|
273
|
+
return PvaLongStringConverter()
|
|
255
274
|
raise TypeError(
|
|
256
275
|
f"{pv} with inferred datatype {format_datatype(inferred_datatype)}"
|
|
257
276
|
f" from {typeid=} {specifier=}"
|