ophyd-async 0.5.2__py3-none-any.whl → 0.6.0__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 +11 -3
- ophyd_async/core/_detector.py +72 -63
- ophyd_async/core/_device.py +13 -15
- ophyd_async/core/_device_save_loader.py +30 -19
- ophyd_async/core/_flyer.py +6 -4
- 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 +63 -0
- ophyd_async/core/_utils.py +24 -28
- ophyd_async/epics/adaravis/_aravis_controller.py +17 -16
- 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 +2 -3
- 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 +19 -14
- ophyd_async/epics/adpilatus/_pilatus_controller.py +18 -16
- ophyd_async/epics/adsimdetector/_sim.py +6 -5
- ophyd_async/epics/adsimdetector/_sim_controller.py +20 -15
- ophyd_async/epics/advimba/_vimba_controller.py +21 -16
- 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 +16 -16
- 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 +14 -15
- ophyd_async/fastcs/panda/_hdf_panda.py +11 -4
- ophyd_async/fastcs/panda/_table.py +111 -138
- ophyd_async/fastcs/panda/_trigger.py +1 -2
- ophyd_async/fastcs/panda/_utils.py +3 -2
- ophyd_async/fastcs/panda/_writer.py +28 -13
- ophyd_async/plan_stubs/_fly.py +16 -16
- 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 +24 -20
- 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-0.5.2.dist-info → ophyd_async-0.6.0.dist-info}/METADATA +46 -45
- ophyd_async-0.6.0.dist-info/RECORD +96 -0
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.6.0.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.6.0.dist-info}/LICENSE +0 -0
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.6.0.dist-info}/entry_points.txt +0 -0
- {ophyd_async-0.5.2.dist-info → ophyd_async-0.6.0.dist-info}/top_level.txt +0 -0
|
@@ -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, field_validator, 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,107 @@ 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
|
-
repeats
|
|
84
|
-
trigger
|
|
85
|
-
position
|
|
86
|
-
time1
|
|
87
|
-
outa1
|
|
88
|
-
outb1
|
|
89
|
-
outc1
|
|
90
|
-
outd1
|
|
91
|
-
oute1
|
|
92
|
-
outf1
|
|
93
|
-
time2
|
|
94
|
-
outa2
|
|
95
|
-
outb2
|
|
96
|
-
outc2
|
|
97
|
-
outd2
|
|
98
|
-
oute2
|
|
99
|
-
outf2
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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[
|
|
55
|
+
np.ndarray[tuple[int], np.dtype[np.unicode_]],
|
|
56
|
+
NpArrayPydanticAnnotation.factory(
|
|
57
|
+
data_type=np.unicode_, dimensions=1, strict_data_typing=False
|
|
58
|
+
),
|
|
59
|
+
Field(default_factory=lambda: np.array([], dtype=np.dtype("<U32"))),
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SeqTable(Table):
|
|
64
|
+
repeats: PydanticNp1DArrayInt32
|
|
65
|
+
trigger: TriggerStr
|
|
66
|
+
position: PydanticNp1DArrayInt32
|
|
67
|
+
time1: PydanticNp1DArrayInt32
|
|
68
|
+
outa1: PydanticNp1DArrayBool
|
|
69
|
+
outb1: PydanticNp1DArrayBool
|
|
70
|
+
outc1: PydanticNp1DArrayBool
|
|
71
|
+
outd1: PydanticNp1DArrayBool
|
|
72
|
+
oute1: PydanticNp1DArrayBool
|
|
73
|
+
outf1: PydanticNp1DArrayBool
|
|
74
|
+
time2: PydanticNp1DArrayInt32
|
|
75
|
+
outa2: PydanticNp1DArrayBool
|
|
76
|
+
outb2: PydanticNp1DArrayBool
|
|
77
|
+
outc2: PydanticNp1DArrayBool
|
|
78
|
+
outd2: PydanticNp1DArrayBool
|
|
79
|
+
oute2: PydanticNp1DArrayBool
|
|
80
|
+
outf2: PydanticNp1DArrayBool
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def row( # type: ignore
|
|
84
|
+
cls,
|
|
85
|
+
*,
|
|
86
|
+
repeats: int = 1,
|
|
87
|
+
trigger: str = SeqTrigger.IMMEDIATE,
|
|
88
|
+
position: int = 0,
|
|
89
|
+
time1: int = 0,
|
|
90
|
+
outa1: bool = False,
|
|
91
|
+
outb1: bool = False,
|
|
92
|
+
outc1: bool = False,
|
|
93
|
+
outd1: bool = False,
|
|
94
|
+
oute1: bool = False,
|
|
95
|
+
outf1: bool = False,
|
|
96
|
+
time2: int = 0,
|
|
97
|
+
outa2: bool = False,
|
|
98
|
+
outb2: bool = False,
|
|
99
|
+
outc2: bool = False,
|
|
100
|
+
outd2: bool = False,
|
|
101
|
+
oute2: bool = False,
|
|
102
|
+
outf2: bool = False,
|
|
103
|
+
) -> "SeqTable":
|
|
104
|
+
if isinstance(trigger, SeqTrigger):
|
|
105
|
+
trigger = trigger.value
|
|
106
|
+
return super().row(**locals())
|
|
107
|
+
|
|
108
|
+
@field_validator("trigger", mode="before")
|
|
109
|
+
@classmethod
|
|
110
|
+
def trigger_to_np_array(cls, trigger_column):
|
|
111
|
+
"""
|
|
112
|
+
The user can provide a list of SeqTrigger enum elements instead of a numpy str.
|
|
113
|
+
"""
|
|
114
|
+
if isinstance(trigger_column, Sequence) and all(
|
|
115
|
+
isinstance(trigger, SeqTrigger) for trigger in trigger_column
|
|
116
|
+
):
|
|
117
|
+
trigger_column = np.array(
|
|
118
|
+
[trigger.value for trigger in trigger_column], dtype=np.dtype("<U32")
|
|
119
|
+
)
|
|
120
|
+
elif isinstance(trigger_column, Sequence) or isinstance(
|
|
121
|
+
trigger_column, np.ndarray
|
|
122
|
+
):
|
|
123
|
+
for trigger in trigger_column:
|
|
124
|
+
SeqTrigger(
|
|
125
|
+
trigger
|
|
126
|
+
) # To check all the given strings are actually `SeqTrigger`s
|
|
127
|
+
else:
|
|
128
|
+
raise ValueError(
|
|
129
|
+
"Expected a numpy array or a sequence of `SeqTrigger`, got "
|
|
130
|
+
f"{type(trigger_column)}."
|
|
131
|
+
)
|
|
132
|
+
return trigger_column
|
|
133
|
+
|
|
134
|
+
@model_validator(mode="after")
|
|
135
|
+
def validate_max_length(self) -> "SeqTable":
|
|
136
|
+
"""
|
|
137
|
+
Used to check max_length. Unfortunately trying the `max_length` arg in
|
|
138
|
+
the pydantic field doesn't work
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
first_length = len(next(iter(self))[1])
|
|
142
|
+
assert 0 <= first_length < 4096, f"Length {first_length} not in range."
|
|
143
|
+
return self
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
from pydantic import BaseModel, Field
|
|
5
4
|
|
|
@@ -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,14 @@ 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,
|
|
47
|
+
iteration: int = 1,
|
|
51
48
|
):
|
|
52
49
|
"""Prepare a hardware triggered flyable and one or more detectors.
|
|
53
50
|
|
|
@@ -70,28 +67,31 @@ def prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
|
|
|
70
67
|
deadtime=deadtime,
|
|
71
68
|
livetime=exposure,
|
|
72
69
|
frame_timeout=frame_timeout,
|
|
70
|
+
iteration=iteration,
|
|
73
71
|
)
|
|
74
72
|
trigger_time = number_of_frames * (exposure + deadtime)
|
|
75
73
|
pre_delay = max(period - 2 * shutter_time - trigger_time, 0)
|
|
76
74
|
|
|
77
|
-
table
|
|
75
|
+
table = (
|
|
78
76
|
# Wait for pre-delay then open shutter
|
|
79
|
-
|
|
77
|
+
SeqTable.row(
|
|
80
78
|
time1=in_micros(pre_delay),
|
|
81
79
|
time2=in_micros(shutter_time),
|
|
82
80
|
outa2=True,
|
|
83
|
-
)
|
|
81
|
+
)
|
|
82
|
+
+
|
|
84
83
|
# Keeping shutter open, do N triggers
|
|
85
|
-
|
|
84
|
+
SeqTable.row(
|
|
86
85
|
repeats=number_of_frames,
|
|
87
86
|
time1=in_micros(exposure),
|
|
88
87
|
outa1=True,
|
|
89
88
|
outb1=True,
|
|
90
89
|
time2=in_micros(deadtime),
|
|
91
90
|
outa2=True,
|
|
92
|
-
)
|
|
91
|
+
)
|
|
92
|
+
+
|
|
93
93
|
# Add the shutter close
|
|
94
|
-
|
|
94
|
+
SeqTable.row(time2=in_micros(shutter_time))
|
|
95
95
|
)
|
|
96
96
|
|
|
97
97
|
table_info = SeqTableInfo(sequence_table=table, repeats=repeats)
|
|
@@ -105,7 +105,7 @@ def prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
|
|
|
105
105
|
def fly_and_collect(
|
|
106
106
|
stream_name: str,
|
|
107
107
|
flyer: StandardFlyer[SeqTableInfo] | StandardFlyer[PcompInfo],
|
|
108
|
-
detectors:
|
|
108
|
+
detectors: list[StandardDetector],
|
|
109
109
|
):
|
|
110
110
|
"""Kickoff, complete and collect with a flyer and multiple detectors.
|
|
111
111
|
|
|
@@ -145,7 +145,7 @@ def fly_and_collect(
|
|
|
145
145
|
def fly_and_collect_with_static_pcomp(
|
|
146
146
|
stream_name: str,
|
|
147
147
|
flyer: StandardFlyer[PcompInfo],
|
|
148
|
-
detectors:
|
|
148
|
+
detectors: list[StandardDetector],
|
|
149
149
|
number_of_pulses: int,
|
|
150
150
|
pulse_width: int,
|
|
151
151
|
rising_edge_step: int,
|
|
@@ -171,7 +171,7 @@ def fly_and_collect_with_static_pcomp(
|
|
|
171
171
|
def time_resolved_fly_and_collect_with_static_seq_table(
|
|
172
172
|
stream_name: str,
|
|
173
173
|
flyer: StandardFlyer[SeqTableInfo],
|
|
174
|
-
detectors:
|
|
174
|
+
detectors: list[StandardDetector],
|
|
175
175
|
number_of_frames: int,
|
|
176
176
|
exposure: float,
|
|
177
177
|
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)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
|
-
from ophyd_async.core import
|
|
3
|
+
from ophyd_async.core import DetectorControl, PathProvider
|
|
4
|
+
from ophyd_async.core._detector import TriggerInfo
|
|
5
5
|
|
|
6
6
|
from ._pattern_generator import PatternGenerator
|
|
7
7
|
|
|
@@ -14,36 +14,40 @@ class PatternDetectorController(DetectorControl):
|
|
|
14
14
|
exposure: float = 0.1,
|
|
15
15
|
) -> None:
|
|
16
16
|
self.pattern_generator: PatternGenerator = pattern_generator
|
|
17
|
-
if exposure is None:
|
|
18
|
-
exposure = 0.1
|
|
19
17
|
self.pattern_generator.set_exposure(exposure)
|
|
20
18
|
self.path_provider: PathProvider = path_provider
|
|
21
|
-
self.task:
|
|
19
|
+
self.task: asyncio.Task | None = None
|
|
22
20
|
super().__init__()
|
|
23
21
|
|
|
24
|
-
async def
|
|
25
|
-
self
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if exposure is None:
|
|
31
|
-
exposure = 0.1
|
|
32
|
-
period: float = exposure + self.get_deadtime(exposure)
|
|
33
|
-
task = asyncio.create_task(
|
|
34
|
-
self._coroutine_for_image_writing(exposure, period, num)
|
|
22
|
+
async def prepare(self, trigger_info: TriggerInfo):
|
|
23
|
+
self._trigger_info = trigger_info
|
|
24
|
+
if self._trigger_info.livetime is None:
|
|
25
|
+
self._trigger_info.livetime = 0.01
|
|
26
|
+
self.period: float = self._trigger_info.livetime + self.get_deadtime(
|
|
27
|
+
trigger_info.livetime
|
|
35
28
|
)
|
|
36
|
-
self.task = task
|
|
37
|
-
return AsyncStatus(task)
|
|
38
29
|
|
|
39
|
-
async def
|
|
30
|
+
async def arm(self):
|
|
31
|
+
assert self._trigger_info.livetime
|
|
32
|
+
assert self.period
|
|
33
|
+
self.task = asyncio.create_task(
|
|
34
|
+
self._coroutine_for_image_writing(
|
|
35
|
+
self._trigger_info.livetime, self.period, self._trigger_info.number
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
async def wait_for_idle(self):
|
|
40
40
|
if self.task:
|
|
41
|
+
await self.task
|
|
42
|
+
|
|
43
|
+
async def disarm(self):
|
|
44
|
+
if self.task and not self.task.done():
|
|
41
45
|
self.task.cancel()
|
|
42
46
|
try:
|
|
43
47
|
await self.task
|
|
44
48
|
except asyncio.CancelledError:
|
|
45
49
|
pass
|
|
46
|
-
|
|
50
|
+
self.task = None
|
|
47
51
|
|
|
48
52
|
def get_deadtime(self, exposure: float | None) -> float:
|
|
49
53
|
return 0.001
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import AsyncGenerator, AsyncIterator
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from event_model import DataKey
|
|
4
4
|
|
|
5
|
-
from ophyd_async.core import DetectorWriter, NameProvider, PathProvider
|
|
5
|
+
from ophyd_async.core import DEFAULT_TIMEOUT, DetectorWriter, NameProvider, PathProvider
|
|
6
6
|
|
|
7
7
|
from ._pattern_generator import PatternGenerator
|
|
8
8
|
|
|
@@ -20,7 +20,7 @@ class PatternDetectorWriter(DetectorWriter):
|
|
|
20
20
|
self.path_provider = path_provider
|
|
21
21
|
self.name_provider = name_provider
|
|
22
22
|
|
|
23
|
-
async def open(self, multiplier: int = 1) ->
|
|
23
|
+
async def open(self, multiplier: int = 1) -> dict[str, DataKey]:
|
|
24
24
|
return await self.pattern_generator.open_file(
|
|
25
25
|
self.path_provider, self.name_provider(), multiplier
|
|
26
26
|
)
|
|
@@ -31,8 +31,11 @@ class PatternDetectorWriter(DetectorWriter):
|
|
|
31
31
|
def collect_stream_docs(self, indices_written: int) -> AsyncIterator:
|
|
32
32
|
return self.pattern_generator.collect_stream_docs(indices_written)
|
|
33
33
|
|
|
34
|
-
def observe_indices_written(
|
|
35
|
-
|
|
34
|
+
async def observe_indices_written(
|
|
35
|
+
self, timeout=DEFAULT_TIMEOUT
|
|
36
|
+
) -> AsyncGenerator[int, None]:
|
|
37
|
+
async for index in self.pattern_generator.observe_indices_written(timeout):
|
|
38
|
+
yield index
|
|
36
39
|
|
|
37
40
|
async def get_indices_written(self) -> int:
|
|
38
41
|
return self.pattern_generator.image_counter
|