Data Acquisition
Traditionally, data acquisition (DAQ) refers to the process of capturing and conditioning signals for recording by a computer. In AxoPy, any source of data generated or influenced by the subject of the experiment is referred to as a DAQ. AxoPy doesn’t include built-in support for hardware aside from things commonly available with just a desktop computer (mouse, keyboard).
AxoPy assumes a fairly simple model for collecting data, based on polling. First, the interface is set up – this might involve initializing a USB interface, connecting to a TCP server, setting up initial parameters, etc. Next, data acquisition is started. Some devices don’t require an explicit start command, but some do. Next, you request data from the device. This is a blocking operation, meaning the request won’t return the data until the data is ready. You’re then free to process, display, or save this data. Then, you request the next batch of data with another request. It is important to make sure consecutive requests occur frequently enough that you don’t fall behind.
For example, imagine you set up a device to acquire data at 1000 Hz in bunches of 100 samples:
from axopy.daq import NoiseGenerator
daq = NoiseGenerator(rate=1000, read_size=100)
daq.start() # NoiseGenerator doesn't require this, but most do
for i in range(10):
data = daq.read()
process_data(data)
daq.stop() # again, NoiseGenerator doesn't require this
Here, you’ll want to ensure that the process_data()
function does not take
longer than 100 ms to complete, or data acquisition will fall behind the rate
at which it is generated.
Some DAQs are built in to AxoPy, but of course not all of them can be. Check out pymcc and pytrigno for a couple examples of working with real data acquisition hardware.
The DaqStream
One thing to notice about the code above is that every time the daq.read()
operation occurs, no other code is being run while waiting for the device to
return the new data. This is sometimes referred to as a blocking operation. In
AxoPy, we usually want some things to be happening while the device is
reading in data in the background. This where the DaqStream
comes in
– a threaded interface to the underlying hardware.
You’ll usually set up your DAQ as above (e.g. daq = NoiseGenerator(...)
),
pass it to the Experiment
as a shared resource, then
make use of the DaqStream
object made available by the Experiment in
your Task implementation. The DaqStream
has a uniform interface so no
matter what kind of hardware you’re using, your task implementation doesn’t
need to care about how that all works. You just start/stop and
connect/disconnect from the stream. In order to facilitate this uniform
interface, the device the DaqStream
wraps needs to expose a specific
API as well. This is defined below:
DAQ API
start() - Called once before the first read.
read() - Request a new buffer of data from the hardware. Parameters (like
the size of the buffer or number of samples to read) should be
set up in the daq constructor. Canonical AxoPy devices generate
a NumPy ndarray with shape (n_channels, n_samples), but the only
real restriction is that your Pipelines can consume the data
generated by your device.
stop() - Called when the user wants the device to stop reading data.
In addition, the DAQ implementation should raise an IOError
if something
goes wrong during data acquisition.
An example of setting up a DaqStream
in the context of a custom
Task
is given in the recipes page.