Jump to content

Programming Reference:GenericFilter Class

From BCI2000 Wiki
Revision as of 14:12, 10 July 2007 by Mellinger (talk | contribs)

The GenericFilter class

GenericFilter is a base class that provides a programming interface for all user code inside the core modules of this BCI2000 implementation. Programming your own data acquisition module, your own filter inside Signal Processing, or your own application, implies deriving your own class from GenericFilter. GenericFilter's member functions represent the various initialization and processing events that occur during system startup and operation (see States of Operation). Your own filter code must implement its own versions of some of these member functions:

Required Members

Constructor

The Constructor, besides its general purpose of initializing member data, declares the parameters and states your filter wants to introduce into the system (the BEGIN_... and END_... macros handle the actual function calls):

 MyFilter::MyFilter()
 : mMyParam( 1 ),
   mMyOtherParam( 0.1 ),
   mCount( 0 )
 {
   BEGIN_PARAMETER_DEFINITIONS
     "MySection int MyParam= 1 "
       "0 0 3 // This is range-checked between 0 and 3",
     "MySection float MyOtherParam= 0.1 "
       "0 % % // This is not automatically range-checked",
   END_PARAMETER_DEFINITIONS

   BEGIN_STATE_DEFINITIONS
     "MyState 1 0 0 0",
   END_STATE_DEFINITIONS
 } 

Preflight

The Preflight function checks whether the preconditions for successful operation are met. This function is called whenever parameter values are re- applied, i.e., whenever the user presses "Set Config", "Start", or "Resume" in the operator window. If Preflight does not report an error via bcierr, this counts as a statement that Initialize and Process will work properly with the current parameters. The first argument to Preflight will inform you about what kind of input signal your filter is going to receive, and your filter is expected to report the properties of its output signal via the second parameter:

 void MyFilter::Preflight( const SignalProperties& inputProperties,
                                 SignalProperties& outputProperties ) const
 {
   PreflightCondition( Parameter( "MyOtherParam" ) > 0.0 );
   PreflightCondition( inputProperties.Channels() > 0 );
   PreflightCondition( inputProperties.MaxElements( 0 ) > 0 );
   outputProperties = inputProperties;
 } 

Note that the const keyword following the function argument list forbids altering any data member of your filter object. This avoids diffusion of initialization code from Initialize into Preflight. If you have your own sub-objects instantiated and maintained by your filter, you should provide them with their own Preflight() const member functions, and call these from your filter's Preflight.

  • Initialize is called after a successful Preflight. Thus, it may safely omit all checks related to parameter consistency. In Initialize, your filter's data members are set to the values implied by the user's choices, or to the initial values required for the filter's operation:
 void MyFilter::Initialize()
 {
   mMyParam = Parameter( "MyParam" );
   mMyOtherParam = Parameter( "MyOtherParam" );
   mCount = 0;
 } 

Process

The Process function is called once for each block of EEG data. It receives an input in its first argument, and sets the output signal to values resulting from the filter operation. In the current BCI2000 implementation, there is a single chain of filters; one filter's output signal is the next filter's input. A filter which does not perform any modification to the signal (e.g., a statistics filter) needs to copy its input signal into the output signal, as in the example:

 void MyFilter::Process( const GenericSignal* inputSignal,
                               GenericSignal* outputSignal )
 {
   if( ( *inputSignal )( 0, 0 ) > mMyOtherParam )
     ++mCount;
   *outputSignal = *inputSignal;
 } 

The Process function should not acquire or release resources (opening/closing files, allocating memory, etc). Natural places for such operations are the Initialize, StartRun, and StopRun member functions.

Optional Member Functions

Other member functions are optional; you may decide whether you override their default implementation with your own version, depending on what your filter needs to do:

StartRun

StartRun is called when the system enters the running state. As opposed to Initialize, which is the place for tasks that need to be performed on each parameter change, StartRun is provided for tasks that need to be performed each time the user clicks "Run" or "Resume" in the operator window. As a canonical example, the DataIOFilter opens a new .dat file from its StartRun member function.

StopRun

StopRun is called each time the system leaves the running state, entering the suspended state. Typically, this happens whenever the user clicks "Suspend" in the operator window. The DataIOFilter provides an example for its usage: This filter closes the current .dat file from its StopRun member function. StopRun is also the only function from which a filter may change a parameter value. Any parameter changes inside StopRun will propagate to the other modules without any explicit request from your side.

Resting

Resting is called instead of Process while the system is in suspended state.

Halt

The Halt function is called before any reconfiguration of the system takes place. If your filter initiates asynchronous operations such as playing a sound file, acquiring EEG data, or executing threads, its Halt member function should terminate all such operations immediately. Failure to do so might result in a non-responding module, or in other errors difficult to track down. For descendants of GenericADC, implementing the Halt function is mandatory.

Destructor

Your filter's Destructor should free all resources acquired in the Constructor or in Initialize. In many cases, freeing of resources will be done automatically if you use direct members instead of pointers, removing the need for a destructor. However, if your filter has a non-empty Halt function, it needs a destructor that calls Halt - this can not be done from the base class destructor because overridden virtual functions cannot be called from base class constructors or destructors.

 MyFilter::~MyFilter()
 {
   Halt();
 }