ophyd-async 0.1.0__py3-none-any.whl → 0.3a1__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.
Files changed (60) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +47 -12
  3. ophyd_async/core/_providers.py +66 -0
  4. ophyd_async/core/async_status.py +7 -5
  5. ophyd_async/core/detector.py +321 -0
  6. ophyd_async/core/device.py +184 -0
  7. ophyd_async/core/device_save_loader.py +286 -0
  8. ophyd_async/core/flyer.py +94 -0
  9. ophyd_async/core/{_device/_signal/signal.py → signal.py} +46 -18
  10. ophyd_async/core/{_device/_backend/signal_backend.py → signal_backend.py} +6 -2
  11. ophyd_async/core/{_device/_backend/sim_signal_backend.py → sim_signal_backend.py} +6 -2
  12. ophyd_async/core/{_device/standard_readable.py → standard_readable.py} +3 -3
  13. ophyd_async/core/utils.py +79 -29
  14. ophyd_async/epics/_backend/_aioca.py +38 -25
  15. ophyd_async/epics/_backend/_p4p.py +62 -27
  16. ophyd_async/epics/_backend/common.py +20 -0
  17. ophyd_async/epics/areadetector/__init__.py +10 -13
  18. ophyd_async/epics/areadetector/controllers/__init__.py +4 -0
  19. ophyd_async/epics/areadetector/controllers/ad_sim_controller.py +52 -0
  20. ophyd_async/epics/areadetector/controllers/pilatus_controller.py +49 -0
  21. ophyd_async/epics/areadetector/drivers/__init__.py +15 -0
  22. ophyd_async/epics/areadetector/drivers/ad_base.py +111 -0
  23. ophyd_async/epics/areadetector/drivers/pilatus_driver.py +18 -0
  24. ophyd_async/epics/areadetector/single_trigger_det.py +4 -4
  25. ophyd_async/epics/areadetector/utils.py +91 -3
  26. ophyd_async/epics/areadetector/writers/__init__.py +5 -0
  27. ophyd_async/epics/areadetector/writers/_hdfdataset.py +10 -0
  28. ophyd_async/epics/areadetector/writers/_hdffile.py +54 -0
  29. ophyd_async/epics/areadetector/writers/hdf_writer.py +133 -0
  30. ophyd_async/epics/areadetector/{nd_file_hdf.py → writers/nd_file_hdf.py} +22 -5
  31. ophyd_async/epics/areadetector/writers/nd_plugin.py +30 -0
  32. ophyd_async/epics/demo/__init__.py +3 -2
  33. ophyd_async/epics/demo/demo_ad_sim_detector.py +35 -0
  34. ophyd_async/epics/motion/motor.py +2 -1
  35. ophyd_async/epics/pvi.py +70 -0
  36. ophyd_async/epics/signal/__init__.py +0 -2
  37. ophyd_async/epics/signal/signal.py +1 -1
  38. ophyd_async/panda/__init__.py +12 -8
  39. ophyd_async/panda/panda.py +43 -134
  40. ophyd_async/panda/panda_controller.py +41 -0
  41. ophyd_async/panda/table.py +158 -0
  42. ophyd_async/panda/utils.py +15 -0
  43. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/METADATA +49 -42
  44. ophyd_async-0.3a1.dist-info/RECORD +56 -0
  45. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/WHEEL +1 -1
  46. ophyd_async/core/_device/__init__.py +0 -0
  47. ophyd_async/core/_device/_backend/__init__.py +0 -0
  48. ophyd_async/core/_device/_signal/__init__.py +0 -0
  49. ophyd_async/core/_device/device.py +0 -60
  50. ophyd_async/core/_device/device_collector.py +0 -121
  51. ophyd_async/core/_device/device_vector.py +0 -14
  52. ophyd_async/epics/areadetector/ad_driver.py +0 -18
  53. ophyd_async/epics/areadetector/directory_provider.py +0 -18
  54. ophyd_async/epics/areadetector/hdf_streamer_det.py +0 -167
  55. ophyd_async/epics/areadetector/nd_plugin.py +0 -13
  56. ophyd_async/epics/signal/pvi_get.py +0 -22
  57. ophyd_async-0.1.0.dist-info/RECORD +0 -45
  58. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/LICENSE +0 -0
  59. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/entry_points.txt +0 -0
  60. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3a1.dist-info}/top_level.txt +0 -0
@@ -92,7 +92,7 @@ def epics_signal_x(write_pv: str) -> SignalX:
92
92
  Parameters
93
93
  ----------
94
94
  write_pv:
95
- The PV to write its initial value to on execute
95
+ The PV to write its initial value to on trigger
96
96
  """
97
97
  backend: SignalBackend = _make_backend(None, write_pv, write_pv)
98
98
  return SignalX(backend)
@@ -1,21 +1,25 @@
1
- from .panda import (
2
- PandA,
3
- PcapBlock,
4
- PulseBlock,
5
- PVIEntry,
6
- SeqBlock,
1
+ from .panda import PandA, PcapBlock, PulseBlock, PVIEntry, SeqBlock, SeqTable
2
+ from .panda_controller import PandaPcapController
3
+ from .table import (
7
4
  SeqTable,
5
+ SeqTableRow,
8
6
  SeqTrigger,
9
- pvi,
7
+ seq_table_from_arrays,
8
+ seq_table_from_rows,
10
9
  )
10
+ from .utils import phase_sorter
11
11
 
12
12
  __all__ = [
13
13
  "PandA",
14
14
  "PcapBlock",
15
15
  "PulseBlock",
16
16
  "PVIEntry",
17
+ "seq_table_from_arrays",
18
+ "seq_table_from_rows",
17
19
  "SeqBlock",
18
20
  "SeqTable",
21
+ "SeqTableRow",
19
22
  "SeqTrigger",
20
- "pvi",
23
+ "phase_sorter",
24
+ "PandaPcapController",
21
25
  ]
@@ -1,28 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
- import atexit
4
3
  import re
5
- from enum import Enum
6
- from typing import (
7
- Callable,
8
- Dict,
9
- FrozenSet,
10
- Optional,
11
- Sequence,
12
- Tuple,
13
- Type,
14
- TypedDict,
15
- cast,
16
- get_args,
17
- get_origin,
18
- get_type_hints,
19
- )
20
-
21
- import numpy as np
22
- import numpy.typing as npt
23
- from p4p.client.thread import Context
4
+ from typing import Dict, Optional, Tuple, cast, get_args, get_origin, get_type_hints
24
5
 
25
6
  from ophyd_async.core import (
7
+ DEFAULT_TIMEOUT,
26
8
  Device,
27
9
  DeviceVector,
28
10
  Signal,
@@ -32,13 +14,8 @@ from ophyd_async.core import (
32
14
  SignalX,
33
15
  SimSignalBackend,
34
16
  )
35
- from ophyd_async.epics.signal import (
36
- epics_signal_r,
37
- epics_signal_rw,
38
- epics_signal_w,
39
- epics_signal_x,
40
- pvi_get,
41
- )
17
+ from ophyd_async.epics.pvi import PVIEntry, make_signal, pvi_get
18
+ from ophyd_async.panda.table import SeqTable
42
19
 
43
20
 
44
21
  class PulseBlock(Device):
@@ -46,56 +23,14 @@ class PulseBlock(Device):
46
23
  width: SignalRW[float]
47
24
 
48
25
 
49
- class SeqTrigger(Enum):
50
- IMMEDIATE = "Immediate"
51
- BITA_0 = "BITA=0"
52
- BITA_1 = "BITA=1"
53
- BITB_0 = "BITB=0"
54
- BITB_1 = "BITB=1"
55
- BITC_0 = "BITC=0"
56
- BITC_1 = "BITC=1"
57
- POSA_GT = "POSA>=POSITION"
58
- POSA_LT = "POSA<=POSITION"
59
- POSB_GT = "POSB>=POSITION"
60
- POSB_LT = "POSB<=POSITION"
61
- POSC_GT = "POSC>=POSITION"
62
- POSC_LT = "POSC<=POSITION"
63
-
64
-
65
- class SeqTable(TypedDict):
66
- repeats: npt.NDArray[np.uint16]
67
- trigger: Sequence[SeqTrigger]
68
- position: npt.NDArray[np.int32]
69
- time1: npt.NDArray[np.uint32]
70
- outa1: npt.NDArray[np.bool_]
71
- outb1: npt.NDArray[np.bool_]
72
- outc1: npt.NDArray[np.bool_]
73
- outd1: npt.NDArray[np.bool_]
74
- oute1: npt.NDArray[np.bool_]
75
- outf1: npt.NDArray[np.bool_]
76
- time2: npt.NDArray[np.uint32]
77
- outa2: npt.NDArray[np.bool_]
78
- outb2: npt.NDArray[np.bool_]
79
- outc2: npt.NDArray[np.bool_]
80
- outd2: npt.NDArray[np.bool_]
81
- oute2: npt.NDArray[np.bool_]
82
- outf2: npt.NDArray[np.bool_]
83
-
84
-
85
26
  class SeqBlock(Device):
86
27
  table: SignalRW[SeqTable]
28
+ active: SignalRW[bool]
87
29
 
88
30
 
89
31
  class PcapBlock(Device):
90
32
  active: SignalR[bool]
91
-
92
-
93
- class PVIEntry(TypedDict, total=False):
94
- d: str
95
- r: str
96
- rw: str
97
- w: str
98
- x: str
33
+ arm: SignalRW[bool]
99
34
 
100
35
 
101
36
  def _block_name_number(block_name: str) -> Tuple[str, Optional[int]]:
@@ -116,7 +51,7 @@ def _block_name_number(block_name: str) -> Tuple[str, Optional[int]]:
116
51
  return block_name, None
117
52
 
118
53
 
119
- def _remove_inconsistent_blocks(pvi_info: Dict[str, PVIEntry]) -> None:
54
+ def _remove_inconsistent_blocks(pvi_info: Optional[Dict[str, PVIEntry]]) -> None:
120
55
  """Remove blocks from pvi information.
121
56
 
122
57
  This is needed because some pandas have 'pcap' and 'pcap1' blocks, which are
@@ -124,6 +59,8 @@ def _remove_inconsistent_blocks(pvi_info: Dict[str, PVIEntry]) -> None:
124
59
  for example.
125
60
 
126
61
  """
62
+ if pvi_info is None:
63
+ return
127
64
  pvi_keys = set(pvi_info.keys())
128
65
  for k in pvi_keys:
129
66
  kn = re.sub(r"\d*$", "", k)
@@ -131,44 +68,14 @@ def _remove_inconsistent_blocks(pvi_info: Dict[str, PVIEntry]) -> None:
131
68
  del pvi_info[k]
132
69
 
133
70
 
134
- async def pvi(pv: str, ctxt: Context, timeout: float = 5.0) -> Dict[str, PVIEntry]:
135
- result = await pvi_get(pv, ctxt, timeout=timeout)
136
- _remove_inconsistent_blocks(result)
137
- return result
138
-
139
-
140
71
  class PandA(Device):
141
- _ctxt: Optional[Context] = None
142
-
143
72
  pulse: DeviceVector[PulseBlock]
144
73
  seq: DeviceVector[SeqBlock]
145
74
  pcap: PcapBlock
146
75
 
147
- def __init__(self, pv: str) -> None:
148
- self._init_prefix = pv
149
- self.pvi_mapping: Dict[FrozenSet[str], Callable[..., Signal]] = {
150
- frozenset({"r", "w"}): lambda dtype, rpv, wpv: epics_signal_rw(
151
- dtype, rpv, wpv
152
- ),
153
- frozenset({"rw"}): lambda dtype, rpv, wpv: epics_signal_rw(dtype, rpv, wpv),
154
- frozenset({"r"}): lambda dtype, rpv, wpv: epics_signal_r(dtype, rpv),
155
- frozenset({"w"}): lambda dtype, rpv, wpv: epics_signal_w(dtype, wpv),
156
- frozenset({"x"}): lambda dtype, rpv, wpv: epics_signal_x(wpv),
157
- }
158
-
159
- @property
160
- def ctxt(self) -> Context:
161
- if PandA._ctxt is None:
162
- PandA._ctxt = Context("pva", nt=False)
163
-
164
- @atexit.register
165
- def _del_ctxt():
166
- # If we don't do this we get messages like this on close:
167
- # Error in sys.excepthook:
168
- # Original exception was:
169
- PandA._ctxt = None
170
-
171
- return PandA._ctxt
76
+ def __init__(self, prefix: str, name: str = "") -> None:
77
+ super().__init__(name)
78
+ self._prefix = prefix
172
79
 
173
80
  def verify_block(self, name: str, num: Optional[int]):
174
81
  """Given a block name and number, return information about a block."""
@@ -186,7 +93,12 @@ class PandA(Device):
186
93
  return block
187
94
 
188
95
  async def _make_block(
189
- self, name: str, num: Optional[int], block_pv: str, sim: bool = False
96
+ self,
97
+ name: str,
98
+ num: Optional[int],
99
+ block_pv: str,
100
+ sim: bool = False,
101
+ timeout: float = DEFAULT_TIMEOUT,
190
102
  ):
191
103
  """Makes a block given a block name containing relevant signals.
192
104
 
@@ -196,7 +108,7 @@ class PandA(Device):
196
108
  block = self.verify_block(name, num)
197
109
 
198
110
  field_annos = get_type_hints(block, globalns=globals())
199
- block_pvi = await pvi(block_pv, self.ctxt) if not sim else None
111
+ block_pvi = await pvi_get(block_pv, timeout=timeout) if not sim else None
200
112
 
201
113
  # finds which fields this class actually has, e.g. delay, width...
202
114
  for sig_name, sig_type in field_annos.items():
@@ -205,7 +117,6 @@ class PandA(Device):
205
117
 
206
118
  # if not in sim mode,
207
119
  if block_pvi:
208
- block_pvi = cast(Dict[str, PVIEntry], block_pvi)
209
120
  # try to get this block in the pvi.
210
121
  entry: Optional[PVIEntry] = block_pvi.get(sig_name)
211
122
  if entry is None:
@@ -214,7 +125,7 @@ class PandA(Device):
214
125
  + f"an {sig_name} signal which has not been retrieved by PVI."
215
126
  )
216
127
 
217
- signal = self._make_signal(entry, args[0] if len(args) > 0 else None)
128
+ signal: Signal = make_signal(entry, args[0] if len(args) > 0 else None)
218
129
 
219
130
  else:
220
131
  backend: SignalBackend = SimSignalBackend(
@@ -229,40 +140,26 @@ class PandA(Device):
229
140
  for attr, attr_pvi in block_pvi.items():
230
141
  if not hasattr(block, attr):
231
142
  # makes any extra signals
232
- signal = self._make_signal(attr_pvi)
233
- setattr(block, attr, signal)
143
+ setattr(block, attr, make_signal(attr_pvi))
234
144
 
235
145
  return block
236
146
 
237
- async def _make_untyped_block(self, block_pv: str):
147
+ async def _make_untyped_block(
148
+ self, block_pv: str, timeout: float = DEFAULT_TIMEOUT
149
+ ):
238
150
  """Populates a block using PVI information.
239
151
 
240
152
  This block is not typed as part of the PandA interface but needs to be
241
153
  included dynamically anyway.
242
154
  """
243
155
  block = Device()
244
- block_pvi: Dict[str, PVIEntry] = await pvi(block_pv, self.ctxt)
156
+ block_pvi: Dict[str, PVIEntry] = await pvi_get(block_pv, timeout=timeout)
245
157
 
246
158
  for signal_name, signal_pvi in block_pvi.items():
247
- signal = self._make_signal(signal_pvi)
248
- setattr(block, signal_name, signal)
159
+ setattr(block, signal_name, make_signal(signal_pvi))
249
160
 
250
161
  return block
251
162
 
252
- def _make_signal(self, signal_pvi: PVIEntry, dtype: Optional[Type] = None):
253
- """Make a signal.
254
-
255
- This assumes datatype is None so it can be used to create dynamic signals.
256
- """
257
- operations = frozenset(signal_pvi.keys())
258
- pvs = [signal_pvi[i] for i in operations] # type: ignore
259
- signal_factory = self.pvi_mapping[operations]
260
-
261
- write_pv = pvs[0]
262
- read_pv = write_pv if len(pvs) == 1 else pvs[1]
263
-
264
- return signal_factory(dtype, "pva://" + read_pv, "pva://" + write_pv)
265
-
266
163
  # TODO redo to set_panda_block? confusing name
267
164
  def set_attribute(self, name: str, num: Optional[int], block: Device):
268
165
  """Set a block on the panda.
@@ -279,7 +176,9 @@ class PandA(Device):
279
176
  else:
280
177
  setattr(self, name, block)
281
178
 
282
- async def connect(self, sim=False) -> None:
179
+ async def connect(
180
+ self, sim: bool = False, timeout: float = DEFAULT_TIMEOUT
181
+ ) -> None:
283
182
  """Initialises all blocks and connects them.
284
183
 
285
184
  First, checks for pvi information. If it exists, make all blocks from this.
@@ -288,7 +187,11 @@ class PandA(Device):
288
187
  If there's no pvi information, that's because we're in sim mode. In that case,
289
188
  makes all required blocks.
290
189
  """
291
- pvi_info = await pvi(self._init_prefix + ":PVI", self.ctxt) if not sim else None
190
+ pvi_info = (
191
+ await pvi_get(self._prefix + "PVI", timeout=timeout) if not sim else None
192
+ )
193
+ _remove_inconsistent_blocks(pvi_info)
194
+
292
195
  hints = {
293
196
  attr_name: attr_type
294
197
  for attr_name, attr_type in get_type_hints(self, globalns=globals()).items()
@@ -302,9 +205,13 @@ class PandA(Device):
302
205
  name, num = _block_name_number(block_name)
303
206
 
304
207
  if name in hints:
305
- block = await self._make_block(name, num, block_pvi["d"])
208
+ block = await self._make_block(
209
+ name, num, block_pvi["d"], timeout=timeout
210
+ )
306
211
  else:
307
- block = await self._make_untyped_block(block_pvi["d"])
212
+ block = await self._make_untyped_block(
213
+ block_pvi["d"], timeout=timeout
214
+ )
308
215
 
309
216
  self.set_attribute(name, num, block)
310
217
 
@@ -325,7 +232,9 @@ class PandA(Device):
325
232
  ], f"Expected PandA to only contain blocks, got {entry}"
326
233
  else:
327
234
  num = 1 if get_origin(hints[block_name]) == DeviceVector else None
328
- block = await self._make_block(block_name, num, "sim://", sim=sim)
235
+ block = await self._make_block(
236
+ block_name, num, "sim://", sim=sim, timeout=timeout
237
+ )
329
238
  self.set_attribute(block_name, num, block)
330
239
 
331
240
  self.set_name(self.name)
@@ -0,0 +1,41 @@
1
+ import asyncio
2
+ from typing import Optional
3
+
4
+ from ophyd_async.core import (
5
+ AsyncStatus,
6
+ DetectorControl,
7
+ DetectorTrigger,
8
+ wait_for_value,
9
+ )
10
+
11
+ from .panda import PcapBlock
12
+
13
+
14
+ class PandaPcapController(DetectorControl):
15
+ def __init__(
16
+ self,
17
+ pcap: PcapBlock,
18
+ ) -> None:
19
+ self.pcap = pcap
20
+
21
+ def get_deadtime(self, exposure: float) -> float:
22
+ return 0.000000008
23
+
24
+ async def arm(
25
+ self,
26
+ num: int,
27
+ trigger: DetectorTrigger = DetectorTrigger.constant_gate,
28
+ exposure: Optional[float] = None,
29
+ ) -> AsyncStatus:
30
+ assert trigger in (
31
+ DetectorTrigger.constant_gate,
32
+ trigger == DetectorTrigger.variable_gate,
33
+ ), "Only constant_gate and variable_gate triggering is supported on the PandA"
34
+ await asyncio.gather(self.pcap.arm.set(True))
35
+ await wait_for_value(self.pcap.active, True, timeout=1)
36
+ return AsyncStatus(wait_for_value(self.pcap.active, False, timeout=None))
37
+
38
+ async def disarm(self):
39
+ await asyncio.gather(self.pcap.arm.set(False))
40
+ await wait_for_value(self.pcap.active, False, timeout=1)
41
+ return AsyncStatus(wait_for_value(self.pcap.active, False, timeout=None))
@@ -0,0 +1,158 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from typing import Optional, Sequence, Type, TypedDict, TypeVar
4
+
5
+ import numpy as np
6
+ import numpy.typing as npt
7
+
8
+
9
+ class SeqTrigger(str, Enum):
10
+ IMMEDIATE = "Immediate"
11
+ BITA_0 = "BITA=0"
12
+ BITA_1 = "BITA=1"
13
+ BITB_0 = "BITB=0"
14
+ BITB_1 = "BITB=1"
15
+ BITC_0 = "BITC=0"
16
+ BITC_1 = "BITC=1"
17
+ POSA_GT = "POSA>=POSITION"
18
+ POSA_LT = "POSA<=POSITION"
19
+ POSB_GT = "POSB>=POSITION"
20
+ POSB_LT = "POSB<=POSITION"
21
+ POSC_GT = "POSC>=POSITION"
22
+ POSC_LT = "POSC<=POSITION"
23
+
24
+
25
+ @dataclass
26
+ class SeqTableRow:
27
+ repeats: int = 1
28
+ trigger: SeqTrigger = SeqTrigger.IMMEDIATE
29
+ position: int = 0
30
+ time1: int = 0
31
+ outa1: bool = False
32
+ outb1: bool = False
33
+ outc1: bool = False
34
+ outd1: bool = False
35
+ oute1: bool = False
36
+ outf1: bool = False
37
+ time2: int = 0
38
+ outa2: bool = False
39
+ outb2: bool = False
40
+ outc2: bool = False
41
+ outd2: bool = False
42
+ oute2: bool = False
43
+ outf2: bool = False
44
+
45
+
46
+ class SeqTable(TypedDict):
47
+ repeats: npt.NDArray[np.uint16]
48
+ trigger: Sequence[SeqTrigger]
49
+ position: npt.NDArray[np.int32]
50
+ time1: npt.NDArray[np.uint32]
51
+ outa1: npt.NDArray[np.bool_]
52
+ outb1: npt.NDArray[np.bool_]
53
+ outc1: npt.NDArray[np.bool_]
54
+ outd1: npt.NDArray[np.bool_]
55
+ oute1: npt.NDArray[np.bool_]
56
+ outf1: npt.NDArray[np.bool_]
57
+ time2: npt.NDArray[np.uint32]
58
+ outa2: npt.NDArray[np.bool_]
59
+ outb2: npt.NDArray[np.bool_]
60
+ outc2: npt.NDArray[np.bool_]
61
+ outd2: npt.NDArray[np.bool_]
62
+ oute2: npt.NDArray[np.bool_]
63
+ outf2: npt.NDArray[np.bool_]
64
+
65
+
66
+ def seq_table_from_rows(*rows: SeqTableRow):
67
+ """
68
+ Constructs a sequence table from a series of rows.
69
+ """
70
+ return seq_table_from_arrays(
71
+ repeats=np.array([row.repeats for row in rows], dtype=np.uint16),
72
+ trigger=[row.trigger for row in rows],
73
+ position=np.array([row.position for row in rows], dtype=np.int32),
74
+ time1=np.array([row.time1 for row in rows], dtype=np.uint32),
75
+ outa1=np.array([row.outa1 for row in rows], dtype=np.bool_),
76
+ outb1=np.array([row.outb1 for row in rows], dtype=np.bool_),
77
+ outc1=np.array([row.outc1 for row in rows], dtype=np.bool_),
78
+ outd1=np.array([row.outd1 for row in rows], dtype=np.bool_),
79
+ oute1=np.array([row.oute1 for row in rows], dtype=np.bool_),
80
+ outf1=np.array([row.outf1 for row in rows], dtype=np.bool_),
81
+ time2=np.array([row.time2 for row in rows], dtype=np.uint32),
82
+ outa2=np.array([row.outa2 for row in rows], dtype=np.bool_),
83
+ outb2=np.array([row.outb2 for row in rows], dtype=np.bool_),
84
+ outc2=np.array([row.outc2 for row in rows], dtype=np.bool_),
85
+ outd2=np.array([row.outd2 for row in rows], dtype=np.bool_),
86
+ oute2=np.array([row.oute2 for row in rows], dtype=np.bool_),
87
+ outf2=np.array([row.outf2 for row in rows], dtype=np.bool_),
88
+ )
89
+
90
+
91
+ T = TypeVar("T", bound=np.generic)
92
+
93
+
94
+ def seq_table_from_arrays(
95
+ *,
96
+ repeats: Optional[npt.NDArray[np.uint16]] = None,
97
+ trigger: Optional[Sequence[SeqTrigger]] = None,
98
+ position: Optional[npt.NDArray[np.int32]] = None,
99
+ time1: Optional[npt.NDArray[np.uint32]] = None,
100
+ outa1: Optional[npt.NDArray[np.bool_]] = None,
101
+ outb1: Optional[npt.NDArray[np.bool_]] = None,
102
+ outc1: Optional[npt.NDArray[np.bool_]] = None,
103
+ outd1: Optional[npt.NDArray[np.bool_]] = None,
104
+ oute1: Optional[npt.NDArray[np.bool_]] = None,
105
+ outf1: Optional[npt.NDArray[np.bool_]] = None,
106
+ time2: npt.NDArray[np.uint32],
107
+ outa2: Optional[npt.NDArray[np.bool_]] = None,
108
+ outb2: Optional[npt.NDArray[np.bool_]] = None,
109
+ outc2: Optional[npt.NDArray[np.bool_]] = None,
110
+ outd2: Optional[npt.NDArray[np.bool_]] = None,
111
+ oute2: Optional[npt.NDArray[np.bool_]] = None,
112
+ outf2: Optional[npt.NDArray[np.bool_]] = None,
113
+ ) -> SeqTable:
114
+ """
115
+ Constructs a sequence table from a series of columns as arrays.
116
+ time2 is the only required argument and must not be None.
117
+ All other provided arguments must be of equal length to time2.
118
+ If any other argument is not given, or else given as None or empty,
119
+ an array of length len(time2) filled with the following is defaulted:
120
+ repeats: 1
121
+ trigger: SeqTrigger.IMMEDIATE
122
+ all others: 0/False as appropriate
123
+ """
124
+ assert time2 is not None, "time2 must be provided"
125
+ length = len(time2)
126
+ assert 0 < length < 4096, f"Length {length} not in range"
127
+
128
+ def or_default(
129
+ value: Optional[npt.NDArray[T]], dtype: Type[T], default_value: int = 0
130
+ ) -> npt.NDArray[T]:
131
+ if value is None or len(value) == 0:
132
+ return np.full(length, default_value, dtype=dtype)
133
+ return value
134
+
135
+ table = SeqTable(
136
+ repeats=or_default(repeats, np.uint16, 1),
137
+ trigger=trigger or [SeqTrigger.IMMEDIATE] * length,
138
+ position=or_default(position, np.int32),
139
+ time1=or_default(time1, np.uint32),
140
+ outa1=or_default(outa1, np.bool_),
141
+ outb1=or_default(outb1, np.bool_),
142
+ outc1=or_default(outc1, np.bool_),
143
+ outd1=or_default(outd1, np.bool_),
144
+ oute1=or_default(oute1, np.bool_),
145
+ outf1=or_default(outf1, np.bool_),
146
+ time2=time2,
147
+ outa2=or_default(outa2, np.bool_),
148
+ outb2=or_default(outb2, np.bool_),
149
+ outc2=or_default(outc2, np.bool_),
150
+ outd2=or_default(outd2, np.bool_),
151
+ oute2=or_default(oute2, np.bool_),
152
+ outf2=or_default(outf2, np.bool_),
153
+ )
154
+ for k, v in table.items():
155
+ size = len(v) # type: ignore
156
+ if size != length:
157
+ raise ValueError(f"{k}: has length {size} not {length}")
158
+ return table
@@ -0,0 +1,15 @@
1
+ from typing import Any, Dict, Sequence
2
+
3
+
4
+ def phase_sorter(panda_signal_values: Dict[str, Any]) -> Sequence[Dict[str, Any]]:
5
+ # Panda has two load phases. If the signal name ends in the string "UNITS",
6
+ # it needs to be loaded first so put in first phase
7
+ phase_1, phase_2 = {}, {}
8
+
9
+ for key, value in panda_signal_values.items():
10
+ if key.endswith("units"):
11
+ phase_1[key] = value
12
+ else:
13
+ phase_2[key] = value
14
+
15
+ return [phase_1, phase_2]