DAE (Data Acquisition Electronics)
The SimpleDae
class is designed to be a configurable DAE object, which will cover the
majority of DAE use-cases within bluesky.
This class uses several objects to configure its behaviour:
The
Controller
is responsible for beginning and ending acquisitions.The
Waiter
is responsible for waiting for an acquisition to be “complete”.The
Reducer
is responsible for publishing data from an acquisition that has just been completed.
This means that SimpleDae
is generic enough to cope with most typical DAE use-casess, for
example running using either one DAE run per scan point, or one DAE period per scan point.
For complex use-cases, particularly those where the DAE may need to start and stop multiple
acquisitions per scan point (e.g. polarization measurements), SimpleDae
is unlikely to be
suitable; instead the Dae
class should be subclassed directly to allow for finer control.
Example configurations
Run-per-point
from ibex_bluesky_core.devices import get_pv_prefix
from ibex_bluesky_core.devices.simpledae import SimpleDae
from ibex_bluesky_core.devices.simpledae.controllers import RunPerPointController
from ibex_bluesky_core.devices.simpledae.waiters import GoodFramesWaiter
from ibex_bluesky_core.devices.simpledae.reducers import GoodFramesNormalizer
prefix = get_pv_prefix()
# One DAE run for each scan point, save the runs after each point.
controller = RunPerPointController(save_run=True)
# Wait for 500 good frames on each run
waiter = GoodFramesWaiter(500)
# Sum spectra 1..99 inclusive, then normalize by total good frames
reducer = GoodFramesNormalizer(
prefix=prefix,
detector_spectra=[i for i in range(1, 100)],
)
dae = SimpleDae(
prefix=prefix,
controller=controller,
waiter=waiter,
reducer=reducer,
)
# Can give signals user-friendly names if desired
controller.run_number.set_name("run number")
reducer.intensity.set_name("normalized counts")
Period-per-point
from ibex_bluesky_core.devices import get_pv_prefix
from ibex_bluesky_core.devices.simpledae import SimpleDae
from ibex_bluesky_core.devices.simpledae.controllers import PeriodPerPointController
from ibex_bluesky_core.devices.simpledae.waiters import PeriodGoodFramesWaiter
from ibex_bluesky_core.devices.simpledae.reducers import PeriodGoodFramesNormalizer
prefix = get_pv_prefix()
# One DAE period for each scan point, save the runs after the scan.
controller = PeriodPerPointController(save_run=True)
# Wait for 500 period good frames on each point
waiter = PeriodGoodFramesWaiter(500)
# Sum spectra 1..99 inclusive, then normalize by period good frames
reducer = PeriodGoodFramesNormalizer(
prefix=prefix,
detector_spectra=[i for i in range(1, 100)],
)
dae = SimpleDae(
prefix=prefix,
controller=controller,
waiter=waiter,
reducer=reducer,
)
Note
You will also need to set up the DAE in advance with enough periods. This can be done from a
plan using yield from bps.mv(dae.number_of_periods, num_points)
before starting the scan.
Mapping to bluesky device model
Start of scan (stage
)
SimpleDae
will call controller.setup()
to allow any pre-scan setup to be done.
For example, this is where the period-per-point controller object will begin a DAE run.
Each scan point (trigger
)
SimpleDae
will call:
controller.start_counting()
to begin counting for a single scan point.waiter.wait()
to wait for that acquisition to completecontroller.stop_counting()
to finish counting for a single scan point.reducer.reduce_data()
to do any necessary post-processing on the raw DAE data (e.g. normalization)
Each scan point (read
)
Any signals marked as “interesting” by the controller, reducer or waiter will be published
in the top-level documents published when read()
ing the SimpleDae
object.
These may correspond to EPICS signals directly from the DAE (e.g. good frames), or may be soft signals derived at runtime (e.g. normalized intensity).
This means that the SimpleDae
object is suitable for use as a detector in most bluesky
plans, and will make an appropriate set of data available in the emitted documents.
End of scan (unstage
)
SimpleDae
will call controller.teardown()
to allow any post-scan teardown to be done.
For example, this is where the period-per-point controller object will end a DAE run.
Controllers
The Controller
class is responsible for starting and stopping acquisitions, in a generic
way.
RunPerPointController
This controller starts and stops a new DAE run for each scan point. It can be configured to either end runs or abort them on completion.
This controller causes the following signals to be published by SimpleDae
:
controller.run_number
- The run number into which data was collected. Only published if runs are being saved.
PeriodPerPointController
This controller begins a single DAE run at the start of a scan, and then counts into a new DAE period for each individual scan point.
The DAE must be configured with enough periods in advance. This is possible to do from a plan as follows:
import bluesky.plan_stubs as bps
import bluesky.plans as bp
from ibex_bluesky_core.devices.simpledae import SimpleDae
from ibex_bluesky_core.devices.block import BlockRw
def plan():
dae: SimpleDae = ...
block: BlockRw = ...
num_points = 20
yield from bps.mv(dae.number_of_periods, num_points)
yield from bp.scan([dae], block, 0, 10, num=num_points)
The controller causes the following signals to be published by SimpleDae
:
simpledae.period_num
- the period number into which this scan point was counted.
Reducers
A Reducer
for a SimpleDae
is responsible for publishing any data derived from the raw
DAE signals. For example, normalizing intensities are implemented as a reducer.
A reducer may produce any number of reduced signals.
GoodFramesNormalizer
This normalizer sums a set of user-defined detector spectra, and then divides by the number of good frames.
Published signals:
simpledae.good_frames
- the number of good frames reported by the DAEreducer.det_counts
- summed detector counts for all of the user-provided spectrareducer.intensity
- normalized intensity (det_counts / good_frames
)reducer.det_counts_stddev
- uncertainty (standard deviation) of the summed detector countsreducer.intensity_stddev
- uncertainty (standard deviation) of the normalised intensity
PeriodGoodFramesNormalizer
Equivalent to the GoodFramesNormalizer
above, but uses good frames only from the current
period. This should be used if a controller which counts into multiple periods is being used.
Published signals:
simpledae.period.good_frames
- the number of good frames reported by the DAEreducer.det_counts
- summed detector counts for all of the user-provided spectrareducer.intensity
- normalized intensity (det_counts / good_frames
)reducer.det_counts_stddev
- uncertainty (standard deviation) of the summed detector countsreducer.intensity_stddev
- uncertainty (standard deviation) of the normalised intensity
DetectorMonitorNormalizer
This normalizer sums a set of user-defined detector spectra, and then divides by the sum of a set of user-defined monitor spectra.
Published signals:
reducer.det_counts
- summed detector counts for the user-provided detector spectrareducer.mon_counts
- summed monitor counts for the user-provided monitor spectrareducer.intensity
- normalized intensity (det_counts / mon_counts
)reducer.det_counts_stddev
- uncertainty (standard deviation) of the summed detector countsreducer.mon_counts_stddev
- uncertainty (standard deviation) of the summed monitor countsreducer.intensity_stddev
- uncertainty (standard deviation) of the normalised intensity
Waiters
A waiter
defines an arbitrary strategy for how long to count at each point.
Some waiters may be very simple, such as waiting for a fixed amount of time or for a number of good frames or microamp-hours. However, it is also possible to define much more sophisticated waiters, for example waiting until sufficient statistics have been collected.
GoodUahWaiter
Waits for a user-specified number of microamp-hours.
Published signals:
simpledae.good_uah
- actual good uAh for this run.
GoodFramesWaiter
Waits for a user-specified number of good frames (in total for the entire run)
Published signals:
simpledae.good_frames
- actual good frames for this run.
GoodFramesWaiter
Waits for a user-specified number of good frames (in the current period)
Published signals:
simpledae.period.good_frames
- actual period good frames for this run.
MEventsWaiter
Waits for a user-specified number of millions of events
Published signals:
simpledae.m_events
- actual period good frames for this run.
TimeWaiter
Waits for a user-specified time duration, irrespective of DAE state.
Does not publish any additional signals.
Dae
(base class, advanced)
Dae
is the principal class in ibex_bluesky_core which exposes configuration settings
and controls from the ISIS data acquisition electronics (DAE). SimpleDae
derives from
DAE, so all of the signals available on Dae
are also available on SimpleDae
.
Note
The Dae
class is not intended to be used directly in scans - it is a low-level class
which directly exposes functionality from the DAE, but has no scanning functionality by
itself.
It is intended that this object is only used directly when:
Configuring DAE settings within a plan.
For advanced use-cases, if arbitrary DAE control is required from a plan - but note that it will usually be better to implement functionality at the device level rather than the plan level.
For other use-cases, a user-facing DAE class such as SimpleDae
is likely to be more
appropriate to use as a detector in a scan - this class cannot be used by itself.
Top-level signals
Some DAE parameters, particularly metadata parameters, are exposed as simple signals,
for example dae.title
or dae.good_uah
.
These signals are directly readable and settable from plans:
import bluesky.plan_stubs as bps
from ibex_bluesky_core.devices.dae.dae import Dae
def plan(dae: Dae):
current_title = yield from bps.rd(dae.title)
yield from bps.mv(dae.title, "new title")
Period-specific signals
For signals which apply to the current period, see dae.period
, which contains signals
such as dae.period.good_uah
(the number of good uamp-hours collected in the current period).
Controlling the DAE directly
It is possible to control the DAE directly using the signals provided by dae.controls
.
The intention is that these signals should be used by higher-level devices, rather than being used by plans directly.
For example, beginning a run is possible via dae.controls.begin_run.trigger()
.
Additional begin_run flags
Options on begin
(for example, beginning a run in paused mode) can be specified
using the dae.controls.begin_run_ex
signal.
Unlike the standard begin_run
signal, this needs to be set()
rather than simply
trigger()
ed, the value on set is a combination of flags from BeginRunExBits
.
DAE Settings
Many signals on the DAE are only available as composite signals - this includes most DAE configuration parameters which are available under the “experiment setup” tab in IBEX, for example wiring/detector/spectra tables, tcb settings, or vetos.
The classes implemented in this way are:
DaeTCBSettings
(dae.tcb_settings
)Parameters which appear under the “time channels” tab in IBEX
DaeSettings
(dae.dae_settings
)Parameters which appear under the “data acquisition” tab in IBEX
DaePeriodSettings
(dae.period_settings
):Parameters which appear under the “periods” tab in IBEX
To read or change these settings from plans, use the associated dataclasses, which are
suffixed with Data
(e.g. DaeSettingsData
is the dataclass corresponding to DaeSettings
):
import bluesky.plan_stubs as bps
from ibex_bluesky_core.devices.dae.dae import Dae
from ibex_bluesky_core.devices.dae.dae_settings import DaeSettingsData
def plan(dae: Dae):
# On read, settings are returned together as an instance of a dataclass.
current_settings: DaeSettingsData = yield from bps.rd(dae.dae_settings)
wiring_table: str = current_settings.wiring_filepath
# On set, any unprovided settings are left unchanged.
yield from bps.mv(dae.dae_settings, DaeSettingsData(
wiring_filepath="a_new_wiring_table.dat",
spectra_filepath="a_new_spectra_table.dat"
))
DAE Spectra
Raw spectra are provided by the DaeSpectra
class. Not all spectra are automatically available
on the base DAE object - user classes will define the specific set of spectra which they are
interested in.
A DaeSpectrum
object provides 3 arrays:
tof
(x-axis): time of flight.counts
(y-axis): number of countsSuitable for summing counts
Will give a discontinuous plot if plotted directly and bin widths are non-uniform.
counts_per_time
(y-axis): number of counts normalized by bin widthNot suitable for summing counts directly
Gives a continuous plot when plotted against x directly.
The Dae
base class does not provide any spectra by default. User-level classes should specify
the set of spectra which they are interested in.