ophyd-async 0.5.2__py3-none-any.whl → 0.7.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 +10 -1
- ophyd_async/__main__.py +12 -4
- ophyd_async/_version.py +2 -2
- ophyd_async/core/__init__.py +15 -7
- ophyd_async/core/_detector.py +133 -87
- ophyd_async/core/_device.py +13 -15
- ophyd_async/core/_device_save_loader.py +30 -19
- ophyd_async/core/_flyer.py +6 -19
- ophyd_async/core/_hdf_dataset.py +8 -9
- ophyd_async/core/_log.py +3 -1
- ophyd_async/core/_mock_signal_backend.py +11 -9
- ophyd_async/core/_mock_signal_utils.py +8 -5
- ophyd_async/core/_protocol.py +7 -7
- ophyd_async/core/_providers.py +11 -11
- ophyd_async/core/_readable.py +30 -22
- ophyd_async/core/_signal.py +52 -51
- ophyd_async/core/_signal_backend.py +20 -7
- ophyd_async/core/_soft_signal_backend.py +62 -32
- ophyd_async/core/_status.py +7 -9
- ophyd_async/core/_table.py +146 -0
- ophyd_async/core/_utils.py +24 -28
- ophyd_async/epics/adaravis/_aravis_controller.py +20 -19
- ophyd_async/epics/adaravis/_aravis_io.py +2 -1
- ophyd_async/epics/adcore/_core_io.py +2 -0
- ophyd_async/epics/adcore/_core_logic.py +4 -5
- ophyd_async/epics/adcore/_hdf_writer.py +19 -8
- ophyd_async/epics/adcore/_single_trigger.py +1 -1
- ophyd_async/epics/adcore/_utils.py +5 -6
- ophyd_async/epics/adkinetix/_kinetix_controller.py +20 -15
- ophyd_async/epics/adpilatus/_pilatus_controller.py +22 -18
- ophyd_async/epics/adsimdetector/_sim.py +7 -6
- ophyd_async/epics/adsimdetector/_sim_controller.py +22 -17
- ophyd_async/epics/advimba/_vimba_controller.py +22 -17
- ophyd_async/epics/demo/_mover.py +4 -5
- ophyd_async/epics/demo/sensor.db +0 -1
- ophyd_async/epics/eiger/_eiger.py +1 -1
- ophyd_async/epics/eiger/_eiger_controller.py +18 -18
- ophyd_async/epics/eiger/_odin_io.py +6 -5
- ophyd_async/epics/motor.py +8 -10
- ophyd_async/epics/pvi/_pvi.py +30 -33
- ophyd_async/epics/signal/_aioca.py +55 -25
- ophyd_async/epics/signal/_common.py +3 -10
- ophyd_async/epics/signal/_epics_transport.py +11 -8
- ophyd_async/epics/signal/_p4p.py +79 -30
- ophyd_async/epics/signal/_signal.py +6 -8
- ophyd_async/fastcs/panda/__init__.py +0 -6
- ophyd_async/fastcs/panda/_control.py +16 -17
- ophyd_async/fastcs/panda/_hdf_panda.py +11 -4
- ophyd_async/fastcs/panda/_table.py +77 -138
- ophyd_async/fastcs/panda/_trigger.py +4 -5
- ophyd_async/fastcs/panda/_utils.py +3 -2
- ophyd_async/fastcs/panda/_writer.py +28 -13
- ophyd_async/plan_stubs/_fly.py +15 -17
- ophyd_async/plan_stubs/_nd_attributes.py +12 -6
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +3 -3
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +27 -21
- ophyd_async/sim/demo/_pattern_detector/_pattern_detector_writer.py +9 -6
- ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +21 -23
- ophyd_async/sim/demo/_sim_motor.py +2 -1
- ophyd_async/tango/__init__.py +45 -0
- ophyd_async/tango/base_devices/__init__.py +4 -0
- ophyd_async/tango/base_devices/_base_device.py +225 -0
- ophyd_async/tango/base_devices/_tango_readable.py +33 -0
- ophyd_async/tango/demo/__init__.py +12 -0
- ophyd_async/tango/demo/_counter.py +37 -0
- ophyd_async/tango/demo/_detector.py +42 -0
- ophyd_async/tango/demo/_mover.py +77 -0
- ophyd_async/tango/demo/_tango/__init__.py +3 -0
- ophyd_async/tango/demo/_tango/_servers.py +108 -0
- ophyd_async/tango/signal/__init__.py +39 -0
- ophyd_async/tango/signal/_signal.py +223 -0
- ophyd_async/tango/signal/_tango_transport.py +764 -0
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.7.0a1.dist-info}/METADATA +50 -45
- ophyd_async-0.7.0a1.dist-info/RECORD +108 -0
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.7.0a1.dist-info}/WHEEL +1 -1
- ophyd_async-0.5.2.dist-info/RECORD +0 -95
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.7.0a1.dist-info}/LICENSE +0 -0
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.7.0a1.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.7.0a1.dist-info}/top_level.txt +0 -0
|
@@ -1,38 +1,37 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
from ophyd_async.core import (
|
|
5
|
-
|
|
6
|
-
DetectorControl,
|
|
4
|
+
DetectorController,
|
|
7
5
|
DetectorTrigger,
|
|
8
6
|
wait_for_value,
|
|
9
7
|
)
|
|
8
|
+
from ophyd_async.core._detector import TriggerInfo
|
|
9
|
+
from ophyd_async.core._status import AsyncStatus
|
|
10
10
|
|
|
11
11
|
from ._block import PcapBlock
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class PandaPcapController(
|
|
14
|
+
class PandaPcapController(DetectorController):
|
|
15
15
|
def __init__(self, pcap: PcapBlock) -> None:
|
|
16
16
|
self.pcap = pcap
|
|
17
|
+
self._arm_status: AsyncStatus | None = None
|
|
17
18
|
|
|
18
|
-
def get_deadtime(self, exposure: float) -> float:
|
|
19
|
+
def get_deadtime(self, exposure: float | None) -> float:
|
|
19
20
|
return 0.000000008
|
|
20
21
|
|
|
21
|
-
async def
|
|
22
|
-
|
|
23
|
-
num: int,
|
|
24
|
-
trigger: DetectorTrigger = DetectorTrigger.constant_gate,
|
|
25
|
-
exposure: Optional[float] = None,
|
|
26
|
-
) -> AsyncStatus:
|
|
27
|
-
assert trigger in (
|
|
22
|
+
async def prepare(self, trigger_info: TriggerInfo):
|
|
23
|
+
assert trigger_info.trigger in (
|
|
28
24
|
DetectorTrigger.constant_gate,
|
|
29
|
-
|
|
25
|
+
DetectorTrigger.variable_gate,
|
|
30
26
|
), "Only constant_gate and variable_gate triggering is supported on the PandA"
|
|
31
|
-
|
|
27
|
+
|
|
28
|
+
async def arm(self):
|
|
29
|
+
self._arm_status = self.pcap.arm.set(True)
|
|
32
30
|
await wait_for_value(self.pcap.active, True, timeout=1)
|
|
33
|
-
return AsyncStatus(wait_for_value(self.pcap.active, False, timeout=None))
|
|
34
31
|
|
|
35
|
-
async def
|
|
32
|
+
async def wait_for_idle(self):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
async def disarm(self):
|
|
36
36
|
await asyncio.gather(self.pcap.arm.set(False))
|
|
37
37
|
await wait_for_value(self.pcap.active, False, timeout=1)
|
|
38
|
-
return AsyncStatus(wait_for_value(self.pcap.active, False, timeout=None))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Sequence
|
|
4
4
|
|
|
5
5
|
from ophyd_async.core import DEFAULT_TIMEOUT, PathProvider, SignalR, StandardDetector
|
|
6
6
|
from ophyd_async.epics.pvi import create_children_from_annotations, fill_pvi_entries
|
|
@@ -36,7 +36,14 @@ class HDFPanda(CommonPandaBlocks, StandardDetector):
|
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
async def connect(
|
|
39
|
-
self,
|
|
40
|
-
|
|
39
|
+
self,
|
|
40
|
+
mock: bool = False,
|
|
41
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
42
|
+
force_reconnect: bool = False,
|
|
43
|
+
):
|
|
44
|
+
# TODO: this doesn't support caching
|
|
45
|
+
# https://github.com/bluesky/ophyd-async/issues/472
|
|
41
46
|
await fill_pvi_entries(self, self._prefix + "PVI", timeout=timeout, mock=mock)
|
|
42
|
-
await super().connect(
|
|
47
|
+
await super().connect(
|
|
48
|
+
mock=mock, timeout=timeout, force_reconnect=force_reconnect
|
|
49
|
+
)
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Sequence
|
|
2
2
|
from enum import Enum
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Annotated
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import numpy.typing as npt
|
|
7
|
-
import
|
|
8
|
-
from
|
|
7
|
+
from pydantic import Field, model_validator
|
|
8
|
+
from pydantic_numpy.helper.annotation import NpArrayPydanticAnnotation
|
|
9
|
+
from typing_extensions import TypedDict
|
|
10
|
+
|
|
11
|
+
from ophyd_async.core import Table
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
class PandaHdf5DatasetType(str, Enum):
|
|
@@ -34,137 +37,73 @@ class SeqTrigger(str, Enum):
|
|
|
34
37
|
POSC_LT = "POSC<=POSITION"
|
|
35
38
|
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
*,
|
|
108
|
-
repeats: Optional[npt.NDArray[np.uint16]] = None,
|
|
109
|
-
trigger: Optional[Sequence[SeqTrigger]] = None,
|
|
110
|
-
position: Optional[npt.NDArray[np.int32]] = None,
|
|
111
|
-
time1: Optional[npt.NDArray[np.uint32]] = None,
|
|
112
|
-
outa1: Optional[npt.NDArray[np.bool_]] = None,
|
|
113
|
-
outb1: Optional[npt.NDArray[np.bool_]] = None,
|
|
114
|
-
outc1: Optional[npt.NDArray[np.bool_]] = None,
|
|
115
|
-
outd1: Optional[npt.NDArray[np.bool_]] = None,
|
|
116
|
-
oute1: Optional[npt.NDArray[np.bool_]] = None,
|
|
117
|
-
outf1: Optional[npt.NDArray[np.bool_]] = None,
|
|
118
|
-
time2: npt.NDArray[np.uint32],
|
|
119
|
-
outa2: Optional[npt.NDArray[np.bool_]] = None,
|
|
120
|
-
outb2: Optional[npt.NDArray[np.bool_]] = None,
|
|
121
|
-
outc2: Optional[npt.NDArray[np.bool_]] = None,
|
|
122
|
-
outd2: Optional[npt.NDArray[np.bool_]] = None,
|
|
123
|
-
oute2: Optional[npt.NDArray[np.bool_]] = None,
|
|
124
|
-
outf2: Optional[npt.NDArray[np.bool_]] = None,
|
|
125
|
-
) -> SeqTable:
|
|
126
|
-
"""
|
|
127
|
-
Constructs a sequence table from a series of columns as arrays.
|
|
128
|
-
time2 is the only required argument and must not be None.
|
|
129
|
-
All other provided arguments must be of equal length to time2.
|
|
130
|
-
If any other argument is not given, or else given as None or empty,
|
|
131
|
-
an array of length len(time2) filled with the following is defaulted:
|
|
132
|
-
repeats: 1
|
|
133
|
-
trigger: SeqTrigger.IMMEDIATE
|
|
134
|
-
all others: 0/False as appropriate
|
|
135
|
-
"""
|
|
136
|
-
assert time2 is not None, "time2 must be provided"
|
|
137
|
-
length = len(time2)
|
|
138
|
-
assert 0 < length < 4096, f"Length {length} not in range"
|
|
139
|
-
|
|
140
|
-
def or_default(
|
|
141
|
-
value: Optional[npt.NDArray[T]], dtype: Type[T], default_value: int = 0
|
|
142
|
-
) -> npt.NDArray[T]:
|
|
143
|
-
if value is None or len(value) == 0:
|
|
144
|
-
return np.full(length, default_value, dtype=dtype)
|
|
145
|
-
return value
|
|
146
|
-
|
|
147
|
-
table = SeqTable(
|
|
148
|
-
repeats=or_default(repeats, np.uint16, 1),
|
|
149
|
-
trigger=trigger or [SeqTrigger.IMMEDIATE] * length,
|
|
150
|
-
position=or_default(position, np.int32),
|
|
151
|
-
time1=or_default(time1, np.uint32),
|
|
152
|
-
outa1=or_default(outa1, np.bool_),
|
|
153
|
-
outb1=or_default(outb1, np.bool_),
|
|
154
|
-
outc1=or_default(outc1, np.bool_),
|
|
155
|
-
outd1=or_default(outd1, np.bool_),
|
|
156
|
-
oute1=or_default(oute1, np.bool_),
|
|
157
|
-
outf1=or_default(outf1, np.bool_),
|
|
158
|
-
time2=time2,
|
|
159
|
-
outa2=or_default(outa2, np.bool_),
|
|
160
|
-
outb2=or_default(outb2, np.bool_),
|
|
161
|
-
outc2=or_default(outc2, np.bool_),
|
|
162
|
-
outd2=or_default(outd2, np.bool_),
|
|
163
|
-
oute2=or_default(oute2, np.bool_),
|
|
164
|
-
outf2=or_default(outf2, np.bool_),
|
|
165
|
-
)
|
|
166
|
-
for k, v in table.items():
|
|
167
|
-
size = len(v) # type: ignore
|
|
168
|
-
if size != length:
|
|
169
|
-
raise ValueError(f"{k}: has length {size} not {length}")
|
|
170
|
-
return table
|
|
40
|
+
PydanticNp1DArrayInt32 = Annotated[
|
|
41
|
+
np.ndarray[tuple[int], np.dtype[np.int32]],
|
|
42
|
+
NpArrayPydanticAnnotation.factory(
|
|
43
|
+
data_type=np.int32, dimensions=1, strict_data_typing=False
|
|
44
|
+
),
|
|
45
|
+
Field(default_factory=lambda: np.array([], np.int32)),
|
|
46
|
+
]
|
|
47
|
+
PydanticNp1DArrayBool = Annotated[
|
|
48
|
+
np.ndarray[tuple[int], np.dtype[np.bool_]],
|
|
49
|
+
NpArrayPydanticAnnotation.factory(
|
|
50
|
+
data_type=np.bool_, dimensions=1, strict_data_typing=False
|
|
51
|
+
),
|
|
52
|
+
Field(default_factory=lambda: np.array([], dtype=np.bool_)),
|
|
53
|
+
]
|
|
54
|
+
TriggerStr = Annotated[Sequence[SeqTrigger], Field(default_factory=list)]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SeqTable(Table):
|
|
58
|
+
repeats: PydanticNp1DArrayInt32
|
|
59
|
+
trigger: TriggerStr
|
|
60
|
+
position: PydanticNp1DArrayInt32
|
|
61
|
+
time1: PydanticNp1DArrayInt32
|
|
62
|
+
outa1: PydanticNp1DArrayBool
|
|
63
|
+
outb1: PydanticNp1DArrayBool
|
|
64
|
+
outc1: PydanticNp1DArrayBool
|
|
65
|
+
outd1: PydanticNp1DArrayBool
|
|
66
|
+
oute1: PydanticNp1DArrayBool
|
|
67
|
+
outf1: PydanticNp1DArrayBool
|
|
68
|
+
time2: PydanticNp1DArrayInt32
|
|
69
|
+
outa2: PydanticNp1DArrayBool
|
|
70
|
+
outb2: PydanticNp1DArrayBool
|
|
71
|
+
outc2: PydanticNp1DArrayBool
|
|
72
|
+
outd2: PydanticNp1DArrayBool
|
|
73
|
+
oute2: PydanticNp1DArrayBool
|
|
74
|
+
outf2: PydanticNp1DArrayBool
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def row( # type: ignore
|
|
78
|
+
cls,
|
|
79
|
+
*,
|
|
80
|
+
repeats: int = 1,
|
|
81
|
+
trigger: str = SeqTrigger.IMMEDIATE,
|
|
82
|
+
position: int = 0,
|
|
83
|
+
time1: int = 0,
|
|
84
|
+
outa1: bool = False,
|
|
85
|
+
outb1: bool = False,
|
|
86
|
+
outc1: bool = False,
|
|
87
|
+
outd1: bool = False,
|
|
88
|
+
oute1: bool = False,
|
|
89
|
+
outf1: bool = False,
|
|
90
|
+
time2: int = 0,
|
|
91
|
+
outa2: bool = False,
|
|
92
|
+
outb2: bool = False,
|
|
93
|
+
outc2: bool = False,
|
|
94
|
+
outd2: bool = False,
|
|
95
|
+
oute2: bool = False,
|
|
96
|
+
outf2: bool = False,
|
|
97
|
+
) -> "SeqTable":
|
|
98
|
+
return Table.row(**locals())
|
|
99
|
+
|
|
100
|
+
@model_validator(mode="after")
|
|
101
|
+
def validate_max_length(self) -> "SeqTable":
|
|
102
|
+
"""
|
|
103
|
+
Used to check max_length. Unfortunately trying the `max_length` arg in
|
|
104
|
+
the pydantic field doesn't work
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
first_length = len(next(iter(self))[1])
|
|
108
|
+
assert 0 <= first_length < 4096, f"Length {first_length} not in range."
|
|
109
|
+
return self
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
from pydantic import BaseModel, Field
|
|
5
4
|
|
|
6
|
-
from ophyd_async.core import
|
|
5
|
+
from ophyd_async.core import FlyerController, wait_for_value
|
|
7
6
|
|
|
8
7
|
from ._block import PcompBlock, PcompDirectionOptions, SeqBlock, TimeUnits
|
|
9
8
|
from ._table import SeqTable
|
|
@@ -15,7 +14,7 @@ class SeqTableInfo(BaseModel):
|
|
|
15
14
|
prescale_as_us: float = Field(default=1, ge=0) # microseconds
|
|
16
15
|
|
|
17
16
|
|
|
18
|
-
class StaticSeqTableTriggerLogic(
|
|
17
|
+
class StaticSeqTableTriggerLogic(FlyerController[SeqTableInfo]):
|
|
19
18
|
def __init__(self, seq: SeqBlock) -> None:
|
|
20
19
|
self.seq = seq
|
|
21
20
|
|
|
@@ -64,7 +63,7 @@ class PcompInfo(BaseModel):
|
|
|
64
63
|
)
|
|
65
64
|
|
|
66
65
|
|
|
67
|
-
class StaticPcompTriggerLogic(
|
|
66
|
+
class StaticPcompTriggerLogic(FlyerController[PcompInfo]):
|
|
68
67
|
def __init__(self, pcomp: PcompBlock) -> None:
|
|
69
68
|
self.pcomp = pcomp
|
|
70
69
|
|
|
@@ -82,7 +81,7 @@ class StaticPcompTriggerLogic(TriggerLogic[PcompInfo]):
|
|
|
82
81
|
await self.pcomp.enable.set("ONE")
|
|
83
82
|
await wait_for_value(self.pcomp.active, True, timeout=1)
|
|
84
83
|
|
|
85
|
-
async def complete(self, timeout:
|
|
84
|
+
async def complete(self, timeout: float | None = None) -> None:
|
|
86
85
|
await wait_for_value(self.pcomp.active, False, timeout=timeout)
|
|
87
86
|
|
|
88
87
|
async def stop(self):
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from typing import Any
|
|
2
3
|
|
|
3
4
|
|
|
4
|
-
def phase_sorter(panda_signal_values:
|
|
5
|
+
def phase_sorter(panda_signal_values: dict[str, Any]) -> Sequence[dict[str, Any]]:
|
|
5
6
|
# Panda has two load phases. If the signal name ends in the string "UNITS",
|
|
6
7
|
# it needs to be loaded first so put in first phase
|
|
7
8
|
phase_1, phase_2 = {}, {}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
from collections.abc import AsyncGenerator, AsyncIterator
|
|
2
3
|
from pathlib import Path
|
|
3
|
-
from typing import AsyncGenerator, AsyncIterator, Dict, List, Optional
|
|
4
4
|
|
|
5
|
-
from bluesky.protocols import
|
|
5
|
+
from bluesky.protocols import StreamAsset
|
|
6
|
+
from event_model import DataKey
|
|
6
7
|
from p4p.client.thread import Context
|
|
7
8
|
|
|
8
9
|
from ophyd_async.core import (
|
|
@@ -20,7 +21,7 @@ from ._block import DataBlock
|
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
class PandaHDFWriter(DetectorWriter):
|
|
23
|
-
_ctxt:
|
|
24
|
+
_ctxt: Context | None = None
|
|
24
25
|
|
|
25
26
|
def __init__(
|
|
26
27
|
self,
|
|
@@ -33,12 +34,12 @@ class PandaHDFWriter(DetectorWriter):
|
|
|
33
34
|
self._prefix = prefix
|
|
34
35
|
self._path_provider = path_provider
|
|
35
36
|
self._name_provider = name_provider
|
|
36
|
-
self._datasets:
|
|
37
|
-
self._file:
|
|
37
|
+
self._datasets: list[HDFDataset] = []
|
|
38
|
+
self._file: HDFFile | None = None
|
|
38
39
|
self._multiplier = 1
|
|
39
40
|
|
|
40
41
|
# Triggered on PCAP arm
|
|
41
|
-
async def open(self, multiplier: int = 1) ->
|
|
42
|
+
async def open(self, multiplier: int = 1) -> dict[str, DataKey]:
|
|
42
43
|
"""Retrieve and get descriptor of all PandA signals marked for capture"""
|
|
43
44
|
|
|
44
45
|
# Ensure flushes are immediate
|
|
@@ -76,7 +77,7 @@ class PandaHDFWriter(DetectorWriter):
|
|
|
76
77
|
|
|
77
78
|
return await self._describe()
|
|
78
79
|
|
|
79
|
-
async def _describe(self) ->
|
|
80
|
+
async def _describe(self) -> dict[str, DataKey]:
|
|
80
81
|
"""
|
|
81
82
|
Return a describe based on the datasets PV
|
|
82
83
|
"""
|
|
@@ -85,9 +86,11 @@ class PandaHDFWriter(DetectorWriter):
|
|
|
85
86
|
describe = {
|
|
86
87
|
ds.data_key: DataKey(
|
|
87
88
|
source=self.panda_data_block.hdf_directory.source,
|
|
88
|
-
shape=ds.shape,
|
|
89
|
+
shape=list(ds.shape),
|
|
89
90
|
dtype="array" if ds.shape != [1] else "number",
|
|
90
|
-
|
|
91
|
+
# PandA data should always be written as Float64
|
|
92
|
+
# Ignore type check until https://github.com/bluesky/event-model/issues/308
|
|
93
|
+
dtype_numpy="<f8", # type: ignore
|
|
91
94
|
external="STREAM:",
|
|
92
95
|
)
|
|
93
96
|
for ds in self._datasets
|
|
@@ -102,15 +105,27 @@ class PandaHDFWriter(DetectorWriter):
|
|
|
102
105
|
|
|
103
106
|
capture_table = await self.panda_data_block.datasets.get_value()
|
|
104
107
|
self._datasets = [
|
|
105
|
-
|
|
108
|
+
# TODO: Update chunk size to read signal once available in IOC
|
|
109
|
+
# Currently PandA IOC sets chunk size to 1024 points per chunk
|
|
110
|
+
HDFDataset(
|
|
111
|
+
dataset_name, "/" + dataset_name, [1], multiplier=1, chunk_shape=(1024,)
|
|
112
|
+
)
|
|
106
113
|
for dataset_name in capture_table["name"]
|
|
107
114
|
]
|
|
108
115
|
|
|
116
|
+
# Warn user if dataset table is empty in PandA
|
|
117
|
+
# i.e. no stream resources will be generated
|
|
118
|
+
if len(self._datasets) == 0:
|
|
119
|
+
self.panda_data_block.log.warning(
|
|
120
|
+
f"PandA {self._name_provider()} DATASETS table is empty! "
|
|
121
|
+
"No stream resource docs will be generated. "
|
|
122
|
+
"Make sure captured positions have their corresponding "
|
|
123
|
+
"*:DATASET PV set to a scientifically relevant name."
|
|
124
|
+
)
|
|
125
|
+
|
|
109
126
|
# Next few functions are exactly the same as AD writer. Could move as default
|
|
110
127
|
# StandardDetector behavior
|
|
111
|
-
async def wait_for_index(
|
|
112
|
-
self, index: int, timeout: Optional[float] = DEFAULT_TIMEOUT
|
|
113
|
-
):
|
|
128
|
+
async def wait_for_index(self, index: int, timeout: float | None = DEFAULT_TIMEOUT):
|
|
114
129
|
def matcher(value: int) -> bool:
|
|
115
130
|
return value >= index
|
|
116
131
|
|
ophyd_async/plan_stubs/_fly.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import List, Optional
|
|
2
|
-
|
|
3
1
|
import bluesky.plan_stubs as bps
|
|
4
2
|
from bluesky.utils import short_uid
|
|
5
3
|
|
|
@@ -15,14 +13,12 @@ from ophyd_async.fastcs.panda import (
|
|
|
15
13
|
PcompInfo,
|
|
16
14
|
SeqTable,
|
|
17
15
|
SeqTableInfo,
|
|
18
|
-
SeqTableRow,
|
|
19
|
-
seq_table_from_rows,
|
|
20
16
|
)
|
|
21
17
|
|
|
22
18
|
|
|
23
19
|
def prepare_static_pcomp_flyer_and_detectors(
|
|
24
20
|
flyer: StandardFlyer[PcompInfo],
|
|
25
|
-
detectors:
|
|
21
|
+
detectors: list[StandardDetector],
|
|
26
22
|
pcomp_info: PcompInfo,
|
|
27
23
|
trigger_info: TriggerInfo,
|
|
28
24
|
):
|
|
@@ -41,13 +37,13 @@ def prepare_static_pcomp_flyer_and_detectors(
|
|
|
41
37
|
|
|
42
38
|
def prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
|
|
43
39
|
flyer: StandardFlyer[SeqTableInfo],
|
|
44
|
-
detectors:
|
|
40
|
+
detectors: list[StandardDetector],
|
|
45
41
|
number_of_frames: int,
|
|
46
42
|
exposure: float,
|
|
47
43
|
shutter_time: float,
|
|
48
44
|
repeats: int = 1,
|
|
49
45
|
period: float = 0.0,
|
|
50
|
-
frame_timeout:
|
|
46
|
+
frame_timeout: float | None = None,
|
|
51
47
|
):
|
|
52
48
|
"""Prepare a hardware triggered flyable and one or more detectors.
|
|
53
49
|
|
|
@@ -65,7 +61,7 @@ def prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
|
|
|
65
61
|
deadtime = max(det.controller.get_deadtime(exposure) for det in detectors)
|
|
66
62
|
|
|
67
63
|
trigger_info = TriggerInfo(
|
|
68
|
-
|
|
64
|
+
number_of_triggers=number_of_frames * repeats,
|
|
69
65
|
trigger=DetectorTrigger.constant_gate,
|
|
70
66
|
deadtime=deadtime,
|
|
71
67
|
livetime=exposure,
|
|
@@ -74,24 +70,26 @@ def prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
|
|
|
74
70
|
trigger_time = number_of_frames * (exposure + deadtime)
|
|
75
71
|
pre_delay = max(period - 2 * shutter_time - trigger_time, 0)
|
|
76
72
|
|
|
77
|
-
table
|
|
73
|
+
table = (
|
|
78
74
|
# Wait for pre-delay then open shutter
|
|
79
|
-
|
|
75
|
+
SeqTable.row(
|
|
80
76
|
time1=in_micros(pre_delay),
|
|
81
77
|
time2=in_micros(shutter_time),
|
|
82
78
|
outa2=True,
|
|
83
|
-
)
|
|
79
|
+
)
|
|
80
|
+
+
|
|
84
81
|
# Keeping shutter open, do N triggers
|
|
85
|
-
|
|
82
|
+
SeqTable.row(
|
|
86
83
|
repeats=number_of_frames,
|
|
87
84
|
time1=in_micros(exposure),
|
|
88
85
|
outa1=True,
|
|
89
86
|
outb1=True,
|
|
90
87
|
time2=in_micros(deadtime),
|
|
91
88
|
outa2=True,
|
|
92
|
-
)
|
|
89
|
+
)
|
|
90
|
+
+
|
|
93
91
|
# Add the shutter close
|
|
94
|
-
|
|
92
|
+
SeqTable.row(time2=in_micros(shutter_time))
|
|
95
93
|
)
|
|
96
94
|
|
|
97
95
|
table_info = SeqTableInfo(sequence_table=table, repeats=repeats)
|
|
@@ -105,7 +103,7 @@ def prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
|
|
|
105
103
|
def fly_and_collect(
|
|
106
104
|
stream_name: str,
|
|
107
105
|
flyer: StandardFlyer[SeqTableInfo] | StandardFlyer[PcompInfo],
|
|
108
|
-
detectors:
|
|
106
|
+
detectors: list[StandardDetector],
|
|
109
107
|
):
|
|
110
108
|
"""Kickoff, complete and collect with a flyer and multiple detectors.
|
|
111
109
|
|
|
@@ -145,7 +143,7 @@ def fly_and_collect(
|
|
|
145
143
|
def fly_and_collect_with_static_pcomp(
|
|
146
144
|
stream_name: str,
|
|
147
145
|
flyer: StandardFlyer[PcompInfo],
|
|
148
|
-
detectors:
|
|
146
|
+
detectors: list[StandardDetector],
|
|
149
147
|
number_of_pulses: int,
|
|
150
148
|
pulse_width: int,
|
|
151
149
|
rising_edge_step: int,
|
|
@@ -171,7 +169,7 @@ def fly_and_collect_with_static_pcomp(
|
|
|
171
169
|
def time_resolved_fly_and_collect_with_static_seq_table(
|
|
172
170
|
stream_name: str,
|
|
173
171
|
flyer: StandardFlyer[SeqTableInfo],
|
|
174
|
-
detectors:
|
|
172
|
+
detectors: list[StandardDetector],
|
|
175
173
|
number_of_frames: int,
|
|
176
174
|
exposure: float,
|
|
177
175
|
shutter_time: float,
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
from
|
|
2
|
-
from xml.etree import
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from xml.etree import ElementTree as ET
|
|
3
3
|
|
|
4
4
|
import bluesky.plan_stubs as bps
|
|
5
5
|
|
|
6
|
-
from ophyd_async.core
|
|
7
|
-
from ophyd_async.epics.adcore
|
|
8
|
-
|
|
6
|
+
from ophyd_async.core import Device
|
|
7
|
+
from ophyd_async.epics.adcore import (
|
|
8
|
+
NDArrayBaseIO,
|
|
9
9
|
NDAttributeDataType,
|
|
10
10
|
NDAttributeParam,
|
|
11
11
|
NDAttributePv,
|
|
12
|
+
NDFileHDFIO,
|
|
12
13
|
)
|
|
13
14
|
|
|
14
15
|
|
|
@@ -48,9 +49,14 @@ def setup_ndattributes(
|
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
def setup_ndstats_sum(detector: Device):
|
|
52
|
+
hdf = getattr(detector, "hdf", None)
|
|
53
|
+
assert isinstance(hdf, NDFileHDFIO), (
|
|
54
|
+
f"Expected {detector.name} to have 'hdf' attribute that is an NDFilHDFIO, "
|
|
55
|
+
f"got {hdf}"
|
|
56
|
+
)
|
|
51
57
|
yield from (
|
|
52
58
|
setup_ndattributes(
|
|
53
|
-
|
|
59
|
+
hdf,
|
|
54
60
|
[
|
|
55
61
|
NDAttributeParam(
|
|
56
62
|
name=f"{detector.name}-sum",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
1
2
|
from pathlib import Path
|
|
2
|
-
from typing import Sequence
|
|
3
3
|
|
|
4
4
|
from ophyd_async.core import (
|
|
5
|
-
AsyncReadable,
|
|
6
5
|
FilenameProvider,
|
|
7
6
|
PathProvider,
|
|
7
|
+
SignalR,
|
|
8
8
|
StandardDetector,
|
|
9
9
|
StaticFilenameProvider,
|
|
10
10
|
StaticPathProvider,
|
|
@@ -19,7 +19,7 @@ class PatternDetector(StandardDetector):
|
|
|
19
19
|
def __init__(
|
|
20
20
|
self,
|
|
21
21
|
path: Path,
|
|
22
|
-
config_sigs: Sequence[
|
|
22
|
+
config_sigs: Sequence[SignalR] = (),
|
|
23
23
|
name: str = "",
|
|
24
24
|
) -> None:
|
|
25
25
|
fp: FilenameProvider = StaticFilenameProvider(name)
|