ophyd-async 0.3a2__py3-none-any.whl → 0.3a3__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 +1 -1
- ophyd_async/core/__init__.py +11 -1
- ophyd_async/core/detector.py +8 -9
- ophyd_async/core/flyer.py +2 -2
- ophyd_async/core/signal.py +102 -7
- ophyd_async/core/signal_backend.py +2 -2
- ophyd_async/core/sim_signal_backend.py +6 -6
- ophyd_async/core/standard_readable.py +211 -24
- ophyd_async/epics/_backend/_aioca.py +6 -6
- ophyd_async/epics/_backend/_p4p.py +17 -11
- ophyd_async/epics/areadetector/__init__.py +4 -0
- ophyd_async/epics/areadetector/aravis.py +7 -9
- ophyd_async/epics/areadetector/controllers/__init__.py +2 -1
- ophyd_async/epics/areadetector/controllers/kinetix_controller.py +49 -0
- ophyd_async/epics/areadetector/controllers/vimba_controller.py +66 -0
- ophyd_async/epics/areadetector/drivers/__init__.py +6 -0
- ophyd_async/epics/areadetector/drivers/kinetix_driver.py +24 -0
- ophyd_async/epics/areadetector/drivers/vimba_driver.py +58 -0
- ophyd_async/epics/areadetector/kinetix.py +46 -0
- ophyd_async/epics/areadetector/pilatus.py +7 -12
- ophyd_async/epics/areadetector/single_trigger_det.py +14 -6
- ophyd_async/epics/areadetector/vimba.py +43 -0
- ophyd_async/epics/areadetector/writers/hdf_writer.py +6 -3
- ophyd_async/epics/areadetector/writers/nd_file_hdf.py +1 -0
- ophyd_async/epics/demo/__init__.py +19 -22
- ophyd_async/epics/motion/motor.py +11 -12
- ophyd_async/epics/signal/signal.py +1 -1
- ophyd_async/log.py +130 -0
- ophyd_async/panda/writers/_hdf_writer.py +3 -3
- ophyd_async/protocols.py +26 -3
- ophyd_async/sim/demo/sim_motor.py +11 -9
- ophyd_async/sim/pattern_generator.py +4 -4
- ophyd_async/sim/sim_pattern_detector_writer.py +2 -2
- ophyd_async/sim/sim_pattern_generator.py +2 -2
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/METADATA +20 -3
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/RECORD +40 -33
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/LICENSE +0 -0
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/WHEEL +0 -0
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.3a2.dist-info → ophyd_async-0.3a3.dist-info}/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ from dataclasses import dataclass
|
|
|
6
6
|
from enum import Enum
|
|
7
7
|
from typing import Any, Dict, List, Optional, Sequence, Type, Union
|
|
8
8
|
|
|
9
|
-
from bluesky.protocols import
|
|
9
|
+
from bluesky.protocols import DataKey, Dtype, Reading
|
|
10
10
|
from p4p import Value
|
|
11
11
|
from p4p.client.asyncio import Context, Subscription
|
|
12
12
|
|
|
@@ -55,7 +55,7 @@ class PvaConverter:
|
|
|
55
55
|
"alarm_severity": -1 if sv > 2 else sv,
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
def
|
|
58
|
+
def get_datakey(self, source: str, value) -> DataKey:
|
|
59
59
|
dtype = specifier_to_dtype[value.type().aspy("value")]
|
|
60
60
|
return {"source": source, "dtype": dtype, "shape": []}
|
|
61
61
|
|
|
@@ -73,7 +73,7 @@ class PvaConverter:
|
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
class PvaArrayConverter(PvaConverter):
|
|
76
|
-
def
|
|
76
|
+
def get_datakey(self, source: str, value) -> DataKey:
|
|
77
77
|
return {"source": source, "dtype": "array", "shape": [len(value["value"])]}
|
|
78
78
|
|
|
79
79
|
|
|
@@ -96,7 +96,7 @@ class PvaNDArrayConverter(PvaConverter):
|
|
|
96
96
|
dims = self._get_dimensions(value)
|
|
97
97
|
return value["value"].reshape(dims)
|
|
98
98
|
|
|
99
|
-
def
|
|
99
|
+
def get_datakey(self, source: str, value) -> DataKey:
|
|
100
100
|
dims = self._get_dimensions(value)
|
|
101
101
|
return {"source": source, "dtype": "array", "shape": dims}
|
|
102
102
|
|
|
@@ -120,7 +120,7 @@ class PvaEnumConverter(PvaConverter):
|
|
|
120
120
|
def value(self, value):
|
|
121
121
|
return list(self.enum_class)[value["value"]["index"]]
|
|
122
122
|
|
|
123
|
-
def
|
|
123
|
+
def get_datakey(self, source: str, value) -> DataKey:
|
|
124
124
|
choices = [e.value for e in self.enum_class]
|
|
125
125
|
return {"source": source, "dtype": "string", "shape": [], "choices": choices}
|
|
126
126
|
|
|
@@ -129,7 +129,7 @@ class PvaEnumBoolConverter(PvaConverter):
|
|
|
129
129
|
def value(self, value):
|
|
130
130
|
return value["value"]["index"]
|
|
131
131
|
|
|
132
|
-
def
|
|
132
|
+
def get_datakey(self, source: str, value) -> DataKey:
|
|
133
133
|
return {"source": source, "dtype": "integer", "shape": []}
|
|
134
134
|
|
|
135
135
|
|
|
@@ -137,7 +137,7 @@ class PvaTableConverter(PvaConverter):
|
|
|
137
137
|
def value(self, value):
|
|
138
138
|
return value["value"].todict()
|
|
139
139
|
|
|
140
|
-
def
|
|
140
|
+
def get_datakey(self, source: str, value) -> DataKey:
|
|
141
141
|
# This is wrong, but defer until we know how to actually describe a table
|
|
142
142
|
return {"source": source, "dtype": "object", "shape": []} # type: ignore
|
|
143
143
|
|
|
@@ -152,7 +152,7 @@ class PvaDictConverter(PvaConverter):
|
|
|
152
152
|
def value(self, value: Value):
|
|
153
153
|
return value.todict()
|
|
154
154
|
|
|
155
|
-
def
|
|
155
|
+
def get_datakey(self, source: str, value) -> DataKey:
|
|
156
156
|
raise NotImplementedError("Describing Dict signals not currently supported")
|
|
157
157
|
|
|
158
158
|
def metadata_fields(self) -> List[str]:
|
|
@@ -216,7 +216,13 @@ def make_converter(datatype: Optional[Type], values: Dict[str, Any]) -> PvaConve
|
|
|
216
216
|
)
|
|
217
217
|
return PvaEnumConverter(get_supported_enum_class(pv, datatype, pv_choices))
|
|
218
218
|
elif "NTScalar" in typeid:
|
|
219
|
-
if
|
|
219
|
+
if (
|
|
220
|
+
datatype
|
|
221
|
+
and not issubclass(typ, datatype)
|
|
222
|
+
and not (
|
|
223
|
+
typ is float and datatype is int
|
|
224
|
+
) # Allow float -> int since prec can be 0
|
|
225
|
+
):
|
|
220
226
|
raise TypeError(f"{pv} has type {typ.__name__} not {datatype.__name__}")
|
|
221
227
|
return PvaConverter()
|
|
222
228
|
elif "NTTable" in typeid:
|
|
@@ -293,9 +299,9 @@ class PvaSignalBackend(SignalBackend[T]):
|
|
|
293
299
|
)
|
|
294
300
|
raise NotConnected(f"pva://{self.write_pv}") from exc
|
|
295
301
|
|
|
296
|
-
async def
|
|
302
|
+
async def get_datakey(self, source: str) -> DataKey:
|
|
297
303
|
value = await self.ctxt.get(self.read_pv)
|
|
298
|
-
return self.converter.
|
|
304
|
+
return self.converter.get_datakey(source, value)
|
|
299
305
|
|
|
300
306
|
def _pva_request_string(self, fields: List[str]) -> str:
|
|
301
307
|
"""
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from .aravis import AravisDetector
|
|
2
|
+
from .kinetix import KinetixDetector
|
|
2
3
|
from .pilatus import PilatusDetector
|
|
3
4
|
from .single_trigger_det import SingleTriggerDet
|
|
4
5
|
from .utils import (
|
|
@@ -9,9 +10,12 @@ from .utils import (
|
|
|
9
10
|
ad_r,
|
|
10
11
|
ad_rw,
|
|
11
12
|
)
|
|
13
|
+
from .vimba import VimbaDetector
|
|
12
14
|
|
|
13
15
|
__all__ = [
|
|
14
16
|
"AravisDetector",
|
|
17
|
+
"KinetixDetector",
|
|
18
|
+
"VimbaDetector",
|
|
15
19
|
"SingleTriggerDet",
|
|
16
20
|
"FileWriteMode",
|
|
17
21
|
"ImageMode",
|
|
@@ -23,16 +23,15 @@ class AravisDetector(StandardDetector, HasHints):
|
|
|
23
23
|
|
|
24
24
|
def __init__(
|
|
25
25
|
self,
|
|
26
|
-
|
|
26
|
+
prefix: str,
|
|
27
27
|
directory_provider: DirectoryProvider,
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
drv_suffix="cam1:",
|
|
29
|
+
hdf_suffix="HDF1:",
|
|
30
|
+
name="",
|
|
30
31
|
gpio_number: AravisController.GPIO_NUMBER = 1,
|
|
31
|
-
**scalar_sigs: str,
|
|
32
32
|
):
|
|
33
|
-
|
|
34
|
-
self.
|
|
35
|
-
self.hdf = hdf
|
|
33
|
+
self.drv = AravisDriver(prefix + drv_suffix)
|
|
34
|
+
self.hdf = NDFileHDF(prefix + hdf_suffix)
|
|
36
35
|
|
|
37
36
|
super().__init__(
|
|
38
37
|
AravisController(self.drv, gpio_number=gpio_number),
|
|
@@ -41,9 +40,8 @@ class AravisDetector(StandardDetector, HasHints):
|
|
|
41
40
|
directory_provider,
|
|
42
41
|
lambda: self.name,
|
|
43
42
|
ADBaseShapeProvider(self.drv),
|
|
44
|
-
**scalar_sigs,
|
|
45
43
|
),
|
|
46
|
-
config_sigs=(self.drv.acquire_time,
|
|
44
|
+
config_sigs=(self.drv.acquire_time,),
|
|
47
45
|
name=name,
|
|
48
46
|
)
|
|
49
47
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from .ad_sim_controller import ADSimController
|
|
2
|
+
from .aravis_controller import AravisController
|
|
2
3
|
from .pilatus_controller import PilatusController
|
|
3
4
|
|
|
4
|
-
__all__ = ["PilatusController", "ADSimController"]
|
|
5
|
+
__all__ = ["PilatusController", "ADSimController", "AravisController"]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
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
|
+
)
|
|
8
|
+
|
|
9
|
+
from ..drivers.kinetix_driver import KinetixDriver, KinetixTriggerMode
|
|
10
|
+
from ..utils import ImageMode, stop_busy_record
|
|
11
|
+
|
|
12
|
+
KINETIX_TRIGGER_MODE_MAP = {
|
|
13
|
+
DetectorTrigger.internal: KinetixTriggerMode.internal,
|
|
14
|
+
DetectorTrigger.constant_gate: KinetixTriggerMode.gate,
|
|
15
|
+
DetectorTrigger.variable_gate: KinetixTriggerMode.gate,
|
|
16
|
+
DetectorTrigger.edge_trigger: KinetixTriggerMode.edge,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class KinetixController(DetectorControl):
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
driver: KinetixDriver,
|
|
24
|
+
) -> None:
|
|
25
|
+
self._drv = driver
|
|
26
|
+
|
|
27
|
+
def get_deadtime(self, exposure: float) -> float:
|
|
28
|
+
return 0.001
|
|
29
|
+
|
|
30
|
+
async def arm(
|
|
31
|
+
self,
|
|
32
|
+
num: int,
|
|
33
|
+
trigger: DetectorTrigger = DetectorTrigger.internal,
|
|
34
|
+
exposure: Optional[float] = None,
|
|
35
|
+
) -> AsyncStatus:
|
|
36
|
+
await asyncio.gather(
|
|
37
|
+
self._drv.trigger_mode.set(KINETIX_TRIGGER_MODE_MAP[trigger]),
|
|
38
|
+
self._drv.num_images.set(num),
|
|
39
|
+
self._drv.image_mode.set(ImageMode.multiple),
|
|
40
|
+
)
|
|
41
|
+
if exposure is not None and trigger not in [
|
|
42
|
+
DetectorTrigger.variable_gate,
|
|
43
|
+
DetectorTrigger.constant_gate,
|
|
44
|
+
]:
|
|
45
|
+
await self._drv.acquire_time.set(exposure)
|
|
46
|
+
return await start_acquiring_driver_and_ensure_status(self._drv)
|
|
47
|
+
|
|
48
|
+
async def disarm(self):
|
|
49
|
+
await stop_busy_record(self._drv.acquire, False, timeout=1)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
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
|
+
)
|
|
8
|
+
|
|
9
|
+
from ..drivers.vimba_driver import (
|
|
10
|
+
VimbaDriver,
|
|
11
|
+
VimbaExposeOutMode,
|
|
12
|
+
VimbaOnOff,
|
|
13
|
+
VimbaTriggerSource,
|
|
14
|
+
)
|
|
15
|
+
from ..utils import ImageMode, stop_busy_record
|
|
16
|
+
|
|
17
|
+
TRIGGER_MODE = {
|
|
18
|
+
DetectorTrigger.internal: VimbaOnOff.off,
|
|
19
|
+
DetectorTrigger.constant_gate: VimbaOnOff.on,
|
|
20
|
+
DetectorTrigger.variable_gate: VimbaOnOff.on,
|
|
21
|
+
DetectorTrigger.edge_trigger: VimbaOnOff.on,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
EXPOSE_OUT_MODE = {
|
|
25
|
+
DetectorTrigger.internal: VimbaExposeOutMode.timed,
|
|
26
|
+
DetectorTrigger.constant_gate: VimbaExposeOutMode.trigger_width,
|
|
27
|
+
DetectorTrigger.variable_gate: VimbaExposeOutMode.trigger_width,
|
|
28
|
+
DetectorTrigger.edge_trigger: VimbaExposeOutMode.timed,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class VimbaController(DetectorControl):
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
driver: VimbaDriver,
|
|
36
|
+
) -> None:
|
|
37
|
+
self._drv = driver
|
|
38
|
+
|
|
39
|
+
def get_deadtime(self, exposure: float) -> float:
|
|
40
|
+
return 0.001
|
|
41
|
+
|
|
42
|
+
async def arm(
|
|
43
|
+
self,
|
|
44
|
+
num: int,
|
|
45
|
+
trigger: DetectorTrigger = DetectorTrigger.internal,
|
|
46
|
+
exposure: Optional[float] = None,
|
|
47
|
+
) -> AsyncStatus:
|
|
48
|
+
await asyncio.gather(
|
|
49
|
+
self._drv.trigger_mode.set(TRIGGER_MODE[trigger]),
|
|
50
|
+
self._drv.expose_mode.set(EXPOSE_OUT_MODE[trigger]),
|
|
51
|
+
self._drv.num_images.set(num),
|
|
52
|
+
self._drv.image_mode.set(ImageMode.multiple),
|
|
53
|
+
)
|
|
54
|
+
if exposure is not None and trigger not in [
|
|
55
|
+
DetectorTrigger.variable_gate,
|
|
56
|
+
DetectorTrigger.constant_gate,
|
|
57
|
+
]:
|
|
58
|
+
await self._drv.acquire_time.set(exposure)
|
|
59
|
+
if trigger != DetectorTrigger.internal:
|
|
60
|
+
self._drv.trig_source.set(VimbaTriggerSource.line1)
|
|
61
|
+
else:
|
|
62
|
+
self._drv.trig_source.set(VimbaTriggerSource.freerun)
|
|
63
|
+
return await start_acquiring_driver_and_ensure_status(self._drv)
|
|
64
|
+
|
|
65
|
+
async def disarm(self):
|
|
66
|
+
await stop_busy_record(self._drv.acquire, False, timeout=1)
|
|
@@ -4,12 +4,18 @@ from .ad_base import (
|
|
|
4
4
|
DetectorState,
|
|
5
5
|
start_acquiring_driver_and_ensure_status,
|
|
6
6
|
)
|
|
7
|
+
from .aravis_driver import AravisDriver
|
|
8
|
+
from .kinetix_driver import KinetixDriver
|
|
7
9
|
from .pilatus_driver import PilatusDriver
|
|
10
|
+
from .vimba_driver import VimbaDriver
|
|
8
11
|
|
|
9
12
|
__all__ = [
|
|
10
13
|
"ADBase",
|
|
11
14
|
"ADBaseShapeProvider",
|
|
12
15
|
"PilatusDriver",
|
|
16
|
+
"AravisDriver",
|
|
17
|
+
"KinetixDriver",
|
|
18
|
+
"VimbaDriver",
|
|
13
19
|
"start_acquiring_driver_and_ensure_status",
|
|
14
20
|
"DetectorState",
|
|
15
21
|
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from ..utils import ad_rw
|
|
4
|
+
from .ad_base import ADBase
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class KinetixTriggerMode(str, Enum):
|
|
8
|
+
internal = "Internal"
|
|
9
|
+
edge = "Rising Edge"
|
|
10
|
+
gate = "Exp. Gate"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class KinetixReadoutMode(str, Enum):
|
|
14
|
+
sensitivity = 1
|
|
15
|
+
speed = 2
|
|
16
|
+
dynamic_range = 3
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class KinetixDriver(ADBase):
|
|
20
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
21
|
+
# self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat")
|
|
22
|
+
self.trigger_mode = ad_rw(KinetixTriggerMode, prefix + "TriggerMode")
|
|
23
|
+
self.mode = ad_rw(KinetixReadoutMode, prefix + "ReadoutPortIdx")
|
|
24
|
+
super().__init__(prefix, name)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from ..utils import ad_rw
|
|
4
|
+
from .ad_base import ADBase
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class VimbaPixelFormat(str, Enum):
|
|
8
|
+
internal = "Mono8"
|
|
9
|
+
ext_enable = "Mono12"
|
|
10
|
+
ext_trigger = "Ext. Trigger"
|
|
11
|
+
mult_trigger = "Mult. Trigger"
|
|
12
|
+
alignment = "Alignment"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class VimbaConvertFormat(str, Enum):
|
|
16
|
+
none = "None"
|
|
17
|
+
mono8 = "Mono8"
|
|
18
|
+
mono16 = "Mono16"
|
|
19
|
+
rgb8 = "RGB8"
|
|
20
|
+
rgb16 = "RGB16"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VimbaTriggerSource(str, Enum):
|
|
24
|
+
freerun = "Freerun"
|
|
25
|
+
line1 = "Line1"
|
|
26
|
+
line2 = "Line2"
|
|
27
|
+
fixed_rate = "FixedRate"
|
|
28
|
+
software = "Software"
|
|
29
|
+
action0 = "Action0"
|
|
30
|
+
action1 = "Action1"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class VimbaOverlap(str, Enum):
|
|
34
|
+
off = "Off"
|
|
35
|
+
prev_frame = "PreviousFrame"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class VimbaOnOff(str, Enum):
|
|
39
|
+
on = "On"
|
|
40
|
+
off = "Off"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class VimbaExposeOutMode(str, Enum):
|
|
44
|
+
timed = "Timed" # Use ExposureTime PV
|
|
45
|
+
trigger_width = "TriggerWidth" # Expose for length of high signal
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class VimbaDriver(ADBase):
|
|
49
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
50
|
+
# self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat")
|
|
51
|
+
self.convert_format = ad_rw(
|
|
52
|
+
VimbaConvertFormat, prefix + "ConvertPixelFormat"
|
|
53
|
+
) # Pixel format of data outputted to AD
|
|
54
|
+
self.trig_source = ad_rw(VimbaTriggerSource, prefix + "TriggerSource")
|
|
55
|
+
self.trigger_mode = ad_rw(VimbaOnOff, prefix + "TriggerMode")
|
|
56
|
+
self.overlap = ad_rw(VimbaOverlap, prefix + "TriggerOverlap")
|
|
57
|
+
self.expose_mode = ad_rw(VimbaExposeOutMode, prefix + "ExposureMode")
|
|
58
|
+
super().__init__(prefix, name)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from bluesky.protocols import HasHints, Hints
|
|
2
|
+
|
|
3
|
+
from ophyd_async.core import DirectoryProvider, 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
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class KinetixDetector(StandardDetector, HasHints):
|
|
13
|
+
"""
|
|
14
|
+
Ophyd-async implementation of an ADKinetix Detector.
|
|
15
|
+
https://github.com/NSLS-II/ADKinetix
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
_controller: KinetixController
|
|
19
|
+
_writer: HDFWriter
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
prefix: str,
|
|
24
|
+
directory_provider: DirectoryProvider,
|
|
25
|
+
drv_suffix="cam1:",
|
|
26
|
+
hdf_suffix="HDF1:",
|
|
27
|
+
name="",
|
|
28
|
+
):
|
|
29
|
+
self.drv = KinetixDriver(prefix + drv_suffix)
|
|
30
|
+
self.hdf = NDFileHDF(prefix + hdf_suffix)
|
|
31
|
+
|
|
32
|
+
super().__init__(
|
|
33
|
+
KinetixController(self.drv),
|
|
34
|
+
HDFWriter(
|
|
35
|
+
self.hdf,
|
|
36
|
+
directory_provider,
|
|
37
|
+
lambda: self.name,
|
|
38
|
+
ADBaseShapeProvider(self.drv),
|
|
39
|
+
),
|
|
40
|
+
config_sigs=(self.drv.acquire_time,),
|
|
41
|
+
name=name,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def hints(self) -> Hints:
|
|
46
|
+
return self._writer.hints
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
from typing import Optional, Sequence
|
|
2
|
-
|
|
3
1
|
from bluesky.protocols import Hints
|
|
4
2
|
|
|
5
3
|
from ophyd_async.core import DirectoryProvider
|
|
6
4
|
from ophyd_async.core.detector import StandardDetector
|
|
7
|
-
from ophyd_async.core.signal import SignalR
|
|
8
5
|
from ophyd_async.epics.areadetector.controllers.pilatus_controller import (
|
|
9
6
|
PilatusController,
|
|
10
7
|
)
|
|
@@ -22,15 +19,14 @@ class PilatusDetector(StandardDetector):
|
|
|
22
19
|
|
|
23
20
|
def __init__(
|
|
24
21
|
self,
|
|
25
|
-
|
|
22
|
+
prefix: str,
|
|
26
23
|
directory_provider: DirectoryProvider,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
**scalar_sigs: str,
|
|
24
|
+
drv_suffix="cam1:",
|
|
25
|
+
hdf_suffix="HDF1:",
|
|
26
|
+
name="",
|
|
31
27
|
):
|
|
32
|
-
self.drv =
|
|
33
|
-
self.hdf =
|
|
28
|
+
self.drv = PilatusDriver(prefix + drv_suffix)
|
|
29
|
+
self.hdf = NDFileHDF(prefix + hdf_suffix)
|
|
34
30
|
|
|
35
31
|
super().__init__(
|
|
36
32
|
PilatusController(self.drv),
|
|
@@ -39,9 +35,8 @@ class PilatusDetector(StandardDetector):
|
|
|
39
35
|
directory_provider,
|
|
40
36
|
lambda: self.name,
|
|
41
37
|
ADBaseShapeProvider(self.drv),
|
|
42
|
-
**scalar_sigs,
|
|
43
38
|
),
|
|
44
|
-
config_sigs=
|
|
39
|
+
config_sigs=(self.drv.acquire_time,),
|
|
45
40
|
name=name,
|
|
46
41
|
)
|
|
47
42
|
|
|
@@ -3,7 +3,13 @@ from typing import Sequence
|
|
|
3
3
|
|
|
4
4
|
from bluesky.protocols import Triggerable
|
|
5
5
|
|
|
6
|
-
from ophyd_async.core import
|
|
6
|
+
from ophyd_async.core import (
|
|
7
|
+
AsyncStatus,
|
|
8
|
+
ConfigSignal,
|
|
9
|
+
HintedSignal,
|
|
10
|
+
SignalR,
|
|
11
|
+
StandardReadable,
|
|
12
|
+
)
|
|
7
13
|
|
|
8
14
|
from .drivers.ad_base import ADBase
|
|
9
15
|
from .utils import ImageMode
|
|
@@ -20,12 +26,14 @@ class SingleTriggerDet(StandardReadable, Triggerable):
|
|
|
20
26
|
) -> None:
|
|
21
27
|
self.drv = drv
|
|
22
28
|
self.__dict__.update(plugins)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
config=[self.drv.acquire_time],
|
|
29
|
+
|
|
30
|
+
self.add_readables(
|
|
31
|
+
[self.drv.array_counter, *read_uncached],
|
|
32
|
+
wrapper=HintedSignal.uncached,
|
|
28
33
|
)
|
|
34
|
+
|
|
35
|
+
self.add_readables([self.drv.acquire_time], wrapper=ConfigSignal)
|
|
36
|
+
|
|
29
37
|
super().__init__(name=name)
|
|
30
38
|
|
|
31
39
|
@AsyncStatus.wrap
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from bluesky.protocols import HasHints, Hints
|
|
2
|
+
|
|
3
|
+
from ophyd_async.core import DirectoryProvider, StandardDetector
|
|
4
|
+
from ophyd_async.epics.areadetector.controllers.vimba_controller import VimbaController
|
|
5
|
+
from ophyd_async.epics.areadetector.drivers import ADBaseShapeProvider
|
|
6
|
+
from ophyd_async.epics.areadetector.drivers.vimba_driver import VimbaDriver
|
|
7
|
+
from ophyd_async.epics.areadetector.writers import HDFWriter, NDFileHDF
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class VimbaDetector(StandardDetector, HasHints):
|
|
11
|
+
"""
|
|
12
|
+
Ophyd-async implementation of an ADVimba Detector.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
_controller: VimbaController
|
|
16
|
+
_writer: HDFWriter
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
prefix: str,
|
|
21
|
+
directory_provider: DirectoryProvider,
|
|
22
|
+
drv_suffix="cam1:",
|
|
23
|
+
hdf_suffix="HDF1:",
|
|
24
|
+
name="",
|
|
25
|
+
):
|
|
26
|
+
self.drv = VimbaDriver(prefix + drv_suffix)
|
|
27
|
+
self.hdf = NDFileHDF(prefix + hdf_suffix)
|
|
28
|
+
|
|
29
|
+
super().__init__(
|
|
30
|
+
VimbaController(self.drv),
|
|
31
|
+
HDFWriter(
|
|
32
|
+
self.hdf,
|
|
33
|
+
directory_provider,
|
|
34
|
+
lambda: self.name,
|
|
35
|
+
ADBaseShapeProvider(self.drv),
|
|
36
|
+
),
|
|
37
|
+
config_sigs=(self.drv.acquire_time,),
|
|
38
|
+
name=name,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def hints(self) -> Hints:
|
|
43
|
+
return self._writer.hints
|
|
@@ -2,7 +2,7 @@ import asyncio
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import AsyncGenerator, AsyncIterator, Dict, List, Optional
|
|
4
4
|
|
|
5
|
-
from bluesky.protocols import
|
|
5
|
+
from bluesky.protocols import DataKey, Hints, StreamAsset
|
|
6
6
|
|
|
7
7
|
from ophyd_async.core import (
|
|
8
8
|
DEFAULT_TIMEOUT,
|
|
@@ -40,7 +40,7 @@ class HDFWriter(DetectorWriter):
|
|
|
40
40
|
self._file: Optional[_HDFFile] = None
|
|
41
41
|
self._multiplier = 1
|
|
42
42
|
|
|
43
|
-
async def open(self, multiplier: int = 1) -> Dict[str,
|
|
43
|
+
async def open(self, multiplier: int = 1) -> Dict[str, DataKey]:
|
|
44
44
|
self._file = None
|
|
45
45
|
info = self._directory_provider()
|
|
46
46
|
await asyncio.gather(
|
|
@@ -52,6 +52,9 @@ class HDFWriter(DetectorWriter):
|
|
|
52
52
|
self.hdf.file_name.set(f"{info.prefix}{self.hdf.name}{info.suffix}"),
|
|
53
53
|
self.hdf.file_template.set("%s/%s.h5"),
|
|
54
54
|
self.hdf.file_write_mode.set(FileWriteMode.stream),
|
|
55
|
+
# Never use custom xml layout file but use the one defined
|
|
56
|
+
# in the source code file NDFileHDF5LayoutXML.cpp
|
|
57
|
+
self.hdf.xml_file_name.set(""),
|
|
55
58
|
)
|
|
56
59
|
|
|
57
60
|
assert (
|
|
@@ -81,7 +84,7 @@ class HDFWriter(DetectorWriter):
|
|
|
81
84
|
)
|
|
82
85
|
)
|
|
83
86
|
describe = {
|
|
84
|
-
ds.name:
|
|
87
|
+
ds.name: DataKey(
|
|
85
88
|
source=self.hdf.full_file_name.source,
|
|
86
89
|
shape=outer_shape + tuple(ds.shape),
|
|
87
90
|
dtype="array" if ds.shape else "number",
|
|
@@ -36,4 +36,5 @@ class NDFileHDF(NDPluginBase):
|
|
|
36
36
|
self.flush_now = epics_signal_rw(bool, prefix + "FlushNow")
|
|
37
37
|
self.array_size0 = ad_r(int, prefix + "ArraySize0")
|
|
38
38
|
self.array_size1 = ad_r(int, prefix + "ArraySize1")
|
|
39
|
+
self.xml_file_name = ad_rw(str, prefix + "XMLFileName")
|
|
39
40
|
super().__init__(prefix, name)
|
|
@@ -16,8 +16,10 @@ from bluesky.protocols import Movable, Stoppable
|
|
|
16
16
|
|
|
17
17
|
from ophyd_async.core import (
|
|
18
18
|
AsyncStatus,
|
|
19
|
+
ConfigSignal,
|
|
19
20
|
Device,
|
|
20
21
|
DeviceVector,
|
|
22
|
+
HintedSignal,
|
|
21
23
|
StandardReadable,
|
|
22
24
|
observe_value,
|
|
23
25
|
)
|
|
@@ -39,26 +41,21 @@ class Sensor(StandardReadable):
|
|
|
39
41
|
|
|
40
42
|
def __init__(self, prefix: str, name="") -> None:
|
|
41
43
|
# Define some signals
|
|
42
|
-
self.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
config=[self.mode],
|
|
48
|
-
)
|
|
44
|
+
with self.add_children_as_readables(HintedSignal):
|
|
45
|
+
self.value = epics_signal_r(float, prefix + "Value")
|
|
46
|
+
with self.add_children_as_readables(ConfigSignal):
|
|
47
|
+
self.mode = epics_signal_rw(EnergyMode, prefix + "Mode")
|
|
48
|
+
|
|
49
49
|
super().__init__(name=name)
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
class SensorGroup(StandardReadable):
|
|
53
53
|
def __init__(self, prefix: str, name: str = "", sensor_count: int = 3) -> None:
|
|
54
|
-
self.
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
with self.add_children_as_readables():
|
|
55
|
+
self.sensors = DeviceVector(
|
|
56
|
+
{i: Sensor(f"{prefix}{i}:") for i in range(1, sensor_count + 1)}
|
|
57
|
+
)
|
|
57
58
|
|
|
58
|
-
# Makes read() produce the values of all sensors
|
|
59
|
-
self.set_readable_signals(
|
|
60
|
-
read=[sensor.value for sensor in self.sensors.values()],
|
|
61
|
-
)
|
|
62
59
|
super().__init__(name)
|
|
63
60
|
|
|
64
61
|
|
|
@@ -67,20 +64,20 @@ class Mover(StandardReadable, Movable, Stoppable):
|
|
|
67
64
|
|
|
68
65
|
def __init__(self, prefix: str, name="") -> None:
|
|
69
66
|
# Define some signals
|
|
67
|
+
with self.add_children_as_readables(HintedSignal):
|
|
68
|
+
self.readback = epics_signal_r(float, prefix + "Readback")
|
|
69
|
+
|
|
70
|
+
with self.add_children_as_readables(ConfigSignal):
|
|
71
|
+
self.velocity = epics_signal_rw(float, prefix + "Velocity")
|
|
72
|
+
self.units = epics_signal_r(str, prefix + "Readback.EGU")
|
|
73
|
+
|
|
70
74
|
self.setpoint = epics_signal_rw(float, prefix + "Setpoint")
|
|
71
|
-
self.readback = epics_signal_r(float, prefix + "Readback")
|
|
72
|
-
self.velocity = epics_signal_rw(float, prefix + "Velocity")
|
|
73
|
-
self.units = epics_signal_r(str, prefix + "Readback.EGU")
|
|
74
75
|
self.precision = epics_signal_r(int, prefix + "Readback.PREC")
|
|
75
76
|
# Signals that collide with standard methods should have a trailing underscore
|
|
76
77
|
self.stop_ = epics_signal_x(prefix + "Stop.PROC")
|
|
77
78
|
# Whether set() should complete successfully or not
|
|
78
79
|
self._set_success = True
|
|
79
|
-
|
|
80
|
-
self.set_readable_signals(
|
|
81
|
-
read=[self.readback],
|
|
82
|
-
config=[self.velocity, self.units],
|
|
83
|
-
)
|
|
80
|
+
|
|
84
81
|
super().__init__(name=name)
|
|
85
82
|
|
|
86
83
|
def set_name(self, name: str):
|