Skip to content

Commit 4ef94ed

Browse files
Merge pull request #28 from ISISComputingGroup/20_user_facing_dae
20 user facing dae
2 parents f358372 + 091cdbd commit 4ef94ed

File tree

17 files changed

+1390
-34
lines changed

17 files changed

+1390
-34
lines changed

doc/devices/dae.md

Lines changed: 254 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,252 @@
1-
# DAE
1+
# DAE (Data Acquisition Electronics)
22

3-
## DaeBase (base class)
3+
The `SimpleDae` class is designed to be a configurable DAE object, which will cover the
4+
majority of DAE use-cases within bluesky.
5+
6+
This class uses several objects to configure its behaviour:
7+
- The `Controller` is responsible for beginning and ending acquisitions.
8+
- The `Waiter` is responsible for waiting for an acquisition to be "complete".
9+
- The `Reducer` is responsible for publishing data from an acquisition that has
10+
just been completed.
11+
12+
This means that `SimpleDae` is generic enough to cope with most typical DAE use-casess, for
13+
example running using either one DAE run per scan point, or one DAE period per scan point.
14+
15+
For complex use-cases, particularly those where the DAE may need to start and stop multiple
16+
acquisitions per scan point (e.g. polarization measurements), `SimpleDae` is unlikely to be
17+
suitable; instead the `Dae` class should be subclassed directly to allow for finer control.
18+
19+
## Example configurations
20+
21+
### Run-per-point
22+
23+
```python
24+
from ibex_bluesky_core.devices import get_pv_prefix
25+
from ibex_bluesky_core.devices.simpledae import SimpleDae
26+
from ibex_bluesky_core.devices.simpledae.controllers import RunPerPointController
27+
from ibex_bluesky_core.devices.simpledae.waiters import GoodFramesWaiter
28+
from ibex_bluesky_core.devices.simpledae.reducers import GoodFramesNormalizer
29+
30+
31+
prefix = get_pv_prefix()
32+
# One DAE run for each scan point, save the runs after each point.
33+
controller = RunPerPointController(save_run=True)
34+
# Wait for 500 good frames on each run
35+
waiter = GoodFramesWaiter(500)
36+
# Sum spectra 1..99 inclusive, then normalize by total good frames
37+
reducer = GoodFramesNormalizer(
38+
prefix=prefix,
39+
detector_spectra=[i for i in range(1, 100)],
40+
)
41+
42+
dae = SimpleDae(
43+
prefix=prefix,
44+
controller=controller,
45+
waiter=waiter,
46+
reducer=reducer,
47+
)
48+
49+
# Can give signals user-friendly names if desired
50+
controller.run_number.set_name("run number")
51+
reducer.intensity.set_name("normalized counts")
52+
```
53+
54+
### Period-per-point
55+
56+
```python
57+
from ibex_bluesky_core.devices import get_pv_prefix
58+
from ibex_bluesky_core.devices.simpledae import SimpleDae
59+
from ibex_bluesky_core.devices.simpledae.controllers import PeriodPerPointController
60+
from ibex_bluesky_core.devices.simpledae.waiters import PeriodGoodFramesWaiter
61+
from ibex_bluesky_core.devices.simpledae.reducers import PeriodGoodFramesNormalizer
62+
63+
64+
prefix = get_pv_prefix()
65+
# One DAE period for each scan point, save the runs after the scan.
66+
controller = PeriodPerPointController(save_run=True)
67+
# Wait for 500 period good frames on each point
68+
waiter = PeriodGoodFramesWaiter(500)
69+
# Sum spectra 1..99 inclusive, then normalize by period good frames
70+
reducer = PeriodGoodFramesNormalizer(
71+
prefix=prefix,
72+
detector_spectra=[i for i in range(1, 100)],
73+
)
74+
75+
dae = SimpleDae(
76+
prefix=prefix,
77+
controller=controller,
78+
waiter=waiter,
79+
reducer=reducer,
80+
)
81+
```
82+
83+
```{note}
84+
You will also need to set up the DAE in advance with enough periods. This can be done from a
85+
plan using `yield from bps.mv(dae.number_of_periods, num_points)` before starting the scan.
86+
```
87+
88+
## Mapping to bluesky device model
89+
90+
### Start of scan (`stage`)
91+
92+
`SimpleDae` will call `controller.setup()` to allow any pre-scan setup to be done.
93+
94+
For example, this is where the period-per-point controller object will begin a DAE run.
95+
96+
### Each scan point (`trigger`)
97+
98+
`SimpleDae` will call:
99+
- `controller.start_counting()` to begin counting for a single scan point.
100+
- `waiter.wait()` to wait for that acquisition to complete
101+
- `controller.stop_counting()` to finish counting for a single scan point.
102+
- `reducer.reduce_data()` to do any necessary post-processing on
103+
the raw DAE data (e.g. normalization)
104+
105+
### Each scan point (`read`)
106+
107+
Any signals marked as "interesting" by the controller, reducer or waiter will be published
108+
in the top-level documents published when `read()`ing the `SimpleDae` object.
109+
110+
These may correspond to EPICS signals directly from the DAE (e.g. good frames), or may be
111+
soft signals derived at runtime (e.g. normalized intensity).
112+
113+
This means that the `SimpleDae` object is suitable for use as a detector in most bluesky
114+
plans, and will make an appropriate set of data available in the emitted documents.
115+
116+
### End of scan (`unstage`)
117+
118+
`SimpleDae` will call `controller.teardown()` to allow any post-scan teardown to be done.
119+
120+
For example, this is where the period-per-point controller object will end a DAE run.
121+
122+
## Controllers
123+
124+
The `Controller` class is responsible for starting and stopping acquisitions, in a generic
125+
way.
126+
127+
### RunPerPointController
128+
129+
This controller starts and stops a new DAE run for each scan point. It can be configured to
130+
either end runs or abort them on completion.
131+
132+
This controller causes the following signals to be published by `SimpleDae`:
133+
134+
- `controller.run_number` - The run number into which data was collected. Only published
135+
if runs are being saved.
136+
137+
### PeriodPerPointController
138+
139+
This controller begins a single DAE run at the start of a scan, and then counts into a new
140+
DAE period for each individual scan point.
141+
142+
The DAE must be configured with enough periods in advance. This is possible to do from a
143+
plan as follows:
144+
145+
```python
146+
import bluesky.plan_stubs as bps
147+
import bluesky.plans as bp
148+
from ibex_bluesky_core.devices.simpledae import SimpleDae
149+
from ibex_bluesky_core.devices.block import BlockRw
150+
151+
152+
def plan():
153+
dae: SimpleDae = ...
154+
block: BlockRw = ...
155+
num_points = 20
156+
yield from bps.mv(dae.number_of_periods, num_points)
157+
yield from bp.scan([dae], block, 0, 10, num=num_points)
158+
```
159+
160+
The controller causes the following signals to be published by `SimpleDae`:
161+
162+
- `simpledae.period_num` - the period number into which this scan point was counted.
163+
164+
## Reducers
165+
166+
A `Reducer` for a `SimpleDae` is responsible for publishing any data derived from the raw
167+
DAE signals. For example, normalizing intensities are implemented as a reducer.
168+
169+
A reducer may produce any number of reduced signals.
170+
171+
### GoodFramesNormalizer
172+
173+
This normalizer sums a set of user-defined detector spectra, and then divides by the number
174+
of good frames.
175+
176+
Published signals:
177+
- `simpledae.good_frames` - the number of good frames reported by the DAE
178+
- `reducer.det_counts` - summed detector counts for all of the user-provided spectra
179+
- `reducer.intensity` - normalized intensity (`det_counts / good_frames`)
180+
181+
### PeriodGoodFramesNormalizer
182+
183+
Equivalent to the `GoodFramesNormalizer` above, but uses good frames only from the current
184+
period. This should be used if a controller which counts into multiple periods is being used.
185+
186+
Published signals:
187+
- `simpledae.period.good_frames` - the number of good frames reported by the DAE
188+
- `reducer.det_counts` - summed detector counts for all of the user-provided spectra
189+
- `reducer.intensity` - normalized intensity (`det_counts / good_frames`)
190+
191+
### DetectorMonitorNormalizer
192+
193+
This normalizer sums a set of user-defined detector spectra, and then divides by the sum
194+
of a set of user-defined monitor spectra.
195+
196+
Published signals:
197+
- `reducer.det_counts` - summed detector counts for the user-provided detector spectra
198+
- `reducer.mon_counts` - summed monitor counts for the user-provided monitor spectra
199+
- `reducer.intensity` - normalized intensity (`det_counts / mon_counts`)
200+
201+
## Waiters
202+
203+
A `waiter` defines an arbitrary strategy for how long to count at each point.
204+
205+
Some waiters may be very simple, such as waiting for a fixed amount of time or for a number
206+
of good frames or microamp-hours. However, it is also possible to define much more
207+
sophisticated waiters, for example waiting until sufficient statistics have been collected.
208+
209+
### GoodUahWaiter
210+
211+
Waits for a user-specified number of microamp-hours.
212+
213+
Published signals:
214+
- `simpledae.good_uah` - actual good uAh for this run.
215+
216+
### GoodFramesWaiter
217+
218+
Waits for a user-specified number of good frames (in total for the entire run)
219+
220+
Published signals:
221+
- `simpledae.good_frames` - actual good frames for this run.
222+
223+
### GoodFramesWaiter
224+
225+
Waits for a user-specified number of good frames (in the current period)
226+
227+
Published signals:
228+
- `simpledae.period.good_frames` - actual period good frames for this run.
229+
230+
### MEventsWaiter
231+
232+
Waits for a user-specified number of millions of events
233+
234+
Published signals:
235+
- `simpledae.m_events` - actual period good frames for this run.
236+
237+
### TimeWaiter
238+
239+
Waits for a user-specified time duration, irrespective of DAE state.
240+
241+
Does not publish any additional signals.
242+
243+
---
244+
245+
## `Dae` (base class, advanced)
4246

5247
`Dae` is the principal class in ibex_bluesky_core which exposes configuration settings
6-
and controls from the ISIS data acquisition electronics (DAE).
248+
and controls from the ISIS data acquisition electronics (DAE). `SimpleDae` derives from
249+
DAE, so all of the signals available on `Dae` are also available on `SimpleDae`.
7250

8251
```{note}
9252
The `Dae` class is not intended to be used directly in scans - it is a low-level class
@@ -16,11 +259,11 @@ and controls from the ISIS data acquisition electronics (DAE).
16259
that it will usually be better to implement functionality at the device level rather
17260
than the plan level.
18261
19-
For other use-cases, a user-facing DAE class is likely to be more appropriate to use
20-
as a detector in a scan - this class cannot be used by itself.
262+
For other use-cases, a user-facing DAE class such as `SimpleDae` is likely to be more
263+
appropriate to use as a detector in a scan - this class cannot be used by itself.
21264
```
22265

23-
## Top-level signals
266+
### Top-level signals
24267

25268
Some DAE parameters, particularly metadata parameters, are exposed as simple signals,
26269
for example `dae.title` or `dae.good_uah`.
@@ -36,13 +279,13 @@ def plan(dae: Dae):
36279
yield from bps.mv(dae.title, "new title")
37280
```
38281

39-
## Period-specific signals
282+
### Period-specific signals
40283

41284
For signals which apply to the current period, see `dae.period`, which contains signals
42285
such as `dae.period.good_uah` (the number of good uamp-hours collected in the current period).
43286

44287

45-
## Controlling the DAE directly
288+
### Controlling the DAE directly
46289

47290
It is possible to control the DAE directly using the signals provided by `dae.controls`.
48291

@@ -51,7 +294,7 @@ used by plans directly.
51294

52295
For example, beginning a run is possible via `dae.controls.begin_run.trigger()`.
53296

54-
### Advanced options
297+
### Additional begin_run flags
55298

56299
Options on `begin` (for example, beginning a run in paused mode) can be specified
57300
using the `dae.controls.begin_run_ex` signal.
@@ -60,7 +303,7 @@ Unlike the standard `begin_run` signal, this needs to be `set()` rather than sim
60303
`trigger()`ed, the value on set is a combination of flags from `BeginRunExBits`.
61304

62305

63-
## DAE Settings
306+
### DAE Settings
64307

65308
Many signals on the DAE are only available as composite signals - this includes most DAE
66309
configuration parameters which are available under the "experiment setup" tab in IBEX, for
@@ -95,7 +338,7 @@ def plan(dae: Dae):
95338
```
96339

97340

98-
## DAE Spectra
341+
### DAE Spectra
99342

100343
Raw spectra are provided by the `DaeSpectra` class. Not all spectra are automatically available
101344
on the base DAE object - user classes will define the specific set of spectra which they are

pyproject.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ dependencies = [
4444
"bluesky",
4545
"ophyd-async[ca]",
4646
"matplotlib",
47-
"numpy"
47+
"numpy",
48+
"scipp",
4849
]
4950

5051
[project.optional-dependencies]
@@ -83,6 +84,12 @@ omit = [
8384

8485
[tool.coverage.report]
8586
fail_under = 100
87+
exclude_lines = [
88+
"pragma: no cover",
89+
"if TYPE_CHECKING:",
90+
"if typing.TYPE_CHECKING:",
91+
"@abstractmethod",
92+
]
8693

8794
[tool.coverage.html]
8895
directory = "coverage_html_report"

0 commit comments

Comments
 (0)