Programming Reference:Events
This page describes how to record asynchronous information ("events") along with brain signal data. This usage of the term "event" is unrelated to "events" as part of BCI2000 API class interfaces.
As data are recorded by data acquisition hardware in chunks, BCI2000 processes them synchronously. Still, recording information that is asynchronous to brain signal data acquisition may be of interest to an experimenter. Typically, mouse or keyboard input may be of interest to record in an experiment, and ideally should provide a temporal resolution that corresponds to the brain signal's sampling rate.
Concept
In the BCI2000 programming API, asynchronous information may be represented as Events.
Here, an Event is an object that
- consists of two elements: a string descriptor and a time stamp, and
- is recorded in a data file together with brain signals.
Implementation
In the current version of BCI2000, events are mapped to state variables. By comparing an event's time stamp to data acquisition time stamps, it is possible to associate events with single samples, and to map an event to a change in a state variable's value.
In order to make events available for writing, they must be first declared as in the following example:
BEGIN_EVENT_DEFINITIONS "MyEvent 10 0 0 0", END_EVENT_DEFINITIONS
Here, an event's definition follows the rules applying to the definition of state variables: first, the name is given, then follow bit width and initial value. At the end, a pair of zeros needs to be added.
Limitations
- Time stamps have millisecond resolution. Up to sampling rates of 1kHz, this is not a practical restriction though.
- Apart from the 1kHz limit given by time stamp resolution, the upper limit of temporal resolution is determined by the amplifier's maximum sampling rate.
- Association of event time stamps with sample positions cannot be more accurate than time stamps assigned to data blocks by the data acquisition module. If data transmission latency between data acquisition hardware, and its associated jitter, are large compared to a single sample's duration, then event positions cannot be expected to be precise. Still, for data acquisition devices that are connected directly to the machine running the data acquisition module (i.e., not over a network connection), and for sampling rates below 1kHz, transmission latency and jitter may be expected to be below a sample's duration.
- Event time stamps must refer to the same physical time base that is used to stamp acquired data packets. This limits the use of events to BCI2000 modules that run on the same machine as the data acquisition module. To ensure correct operation also in situations where processing modules are distributed across multiple machines in a network, the event API may be used only in data acquisition modules.
- The bcievent asynchronous stream interface should only be called between "StartRun" and "StopRun". Logging events with bcievent outside of a run could result in delayed state logging through the bcievent interface.
Event API
When including the src/shared/accessors/BCIEvent.h header file into a source code file, a symbol bcievent will be available. The bcievent symbol behaves like a std::ostream; writing to that stream creates an event, fills its descriptor with whatever has been written into the stream, and stamps the event with current time. The bcievent symbol's ostream behavior makes it convenient to include numeric information into the event's string descriptor. To allow for true asynchrony, BCI2000 events may be issued from any thread within a BCI2000 module, i.e. the bcievent interface is thread-safe.
Descriptor Syntax
An event descriptor consists of a name, a value, and optionally a duration, with data fields being separated with white space:
<name> <value> [<duration>]
Currently, events are mapped to state variables, so the following restrictions apply:
- The name field must match the name of an event kind state variable defined within BEGIN_EVENT_DEFINITIONS/END_EVENT_DEFINITIONS, and that state variable must exist in the system before it appears in an event.
- The value field must specify an unsigned integer value that "fits into" the number of bits reserved for the corresponding state variable.
- Currently, the duration must either be omitted, or 0. When mapping the event to a state variable, a duration of 0 means that the state variable is set to the specified value for a single sample only, and to zero for subsequent samples. A non-specified duration means that the state variable is changed, and keeps its value for subsequent samples.
Extended Syntax
bcievent is actually a macro hiding the definition of an object of type BCIEvent. You may create such an object yourself, which is useful if you want to define the time stamp to use rather than have it taken automatically for you.
BCIEvent(PrecisionTime::Now()) << "MyEvent " << value;
will match the default behavior: The event is initialized with the current time stamp at creation.
BCIEvent(PrecisionTime::Now() + 5) << "MyEvent " << value;
will delay the event by 5ms. Be warned, however, that time stamps extending beyond the sample block currently digitized will not work properly, due to the way how events are aligned with data in time.
Example
In this example, an event "MyEvent" will be created, its value field set to currentValue, and its duration field empty. Please note that the name is followed with a space character separating name and value. The event will be time stamped when it is created, and will be sent when it goes out of scope, or is flushed explicitly. Unless there is time-consuming code between event creation, and the end of scope, there is no need to flush it explicitly.
#include "BCIEvent.h" ... { int currentValue; ... bcievent << "MyEvent " << currentValue; // MyEvent is time stamped here bcievent << "AnotherEvent " << currentValue << std::flush; // AnotherEvent is time stamped and sent here BCIEvent(PrecisionTime::Now()+5) << "Event3 " << currentValue; // Event3 is time stamped here with a delayed time stamp ... } // MyEvent and Event3 are sent here
See also
Programming Reference:Environment Class, Technical Reference:State Definition, Programming Tutorial:Implementing an Input Logger
Events are used to implement keyboard, mouse, and joystick logging: User Reference:Logging Input