Signal processing

Table of Contents

Introduction

In BCI2000, a processing module just calls a number of individual filters, which makes it very easy to switch two filters or replace one by another. Moreover, one filter can be used by many modules. The signal blocks are transmitted automatically from one filter to another. The following figure shows a diagram representing the structure of a signal processing module.

Signal Processing Module

As an example, the Spatial Filter is almost always used. Each module that needs it just calls it. There is no need to copy it or duplicate the code. More details can be found in the included filters section.

We will start by creating the signal processing module, which groups the different filters together, and see how we can use already created filters. Then we'll see how to create a new filter and integrate it to our signal processing module.

The main module

To create a signal processing module, launch C++ Builder and create a new project by clicking “Project”“Add New Project…“ and then “Application”. This will also create a file and a form. You can close the form and the file. You will be prompted with a message to save the file. Do not; it is useless. Instead, create a new file by clicking “File”“New”“Other…“ and then “Cpp file”. Copy the following code in the file (you can copy/paste from almost any other Signal Processing main file), then save the project (“File” “Save Project as”) in its own folder (under “src/contrib/signalprocessing/”) and name it accordingly (the project is the module). You will also be asked to save the file. You should give it the same name.

YourProject.cpp:

#include "PCHIncludes.h"
#pragma hdrstop

#include 
#include "CoreModuleVCL.h"

WINAPI
WinMain( HINSTANCE, HINSTANCE, LPSTR, int ) {
	try {
		Application->Initialize();
		CoreModuleVCL().Run(_argc, _argv );
	}
	catch (Exception &exception) {
		Application->ShowException(&exception);
	}
	return 0;
}


The PCHincludes header is necessary for your module to be able to call BCI2000 functions in order to follow the protocol (such as the “Filter” function without which you could not use your filters). The pragma instruction is a question of precompilation options. The “vcl” includes are for the user interface (Borland VCL Library).

The next step is to create a “PipeDefinition” file. The name of the file is important. Create a new “Cpp file” in the same project and start by writing the following lines:

PipeDefinition.cpp:

#include "PCHIncludes.h"
#pragama hdrstop

#include "SpatialFilter.h"

Filter( SpatialFilter, 2.C );

This file is the description of the filter chain. Include all the filters you want or need, and call them with the “Filter” function. The “2.C” part is the order in which the filters will be set. The first digit should follow the following protocol \cite{filterpos}:

  • filters in source modules
  • filters in processing modules
  • filters in applications

This means all filters called in the signal processing module will have a rank of 2 (while application filters have a rank of 3). The filters are then applied according to the relative alphabetical position of their letter (2.B comes before 2.E).

For now, the Spatial Filter is enough to test your module. You can add other filters later.

The last step is to set up the links. Choose “Project”“Add to project” and choose the PipeDefinition file you've just created. Then repeat this for all cpp files in the different folders under “src/shared”. The reason for this is because the “PCHincludes” file you included is the tip of the iceberg that is the hierarchy of files that compose BCI2000. It is possible that you will not be using all of them, but it is easier to add everything than to sort through them. If you miss any, you will be notified by C++ Builder at the end of the compilation. It will say ”[Linker Error] unresolved external…” and then the name of the missing file, followed by error codes.

You will also have to make sure the following options are correct. Clic “Project” “Options”.

  • Compiler: In the “File name” field: ”..\textbackslash ..\textbackslash..\textbackslash shared\textbackslash obj\textbackslash bci2000.csm”
  • Linker: Under Linking, the “Use Dynamic RTL” box is \uline{not} checked.
  • Directories/Conditionals
  • The “Include path” field has the following, in any order:
    • ..\..\..\shared
    • ..\..\..\shared\accessors
    • ..\..\..\shared\bcistream
    • ..\..\..\shared\config
    • ..\..\..\shared\fileio
    • ..\..\..\shared\gui
    • ..\..\..\shared\modules
    • ..\..\..\shared\modules\textbackslash signalprocessing
    • ..\..\..\shared\types
    • ..\..\..\shared\utils
    • ..\..\..\shared\utils\textbackslash Expression
    • ..\..\..\extlib\matlab
    • \$ (BCB)\include
    • \$ (BCB)\include\vcl
    • The folder containing your module (probably contrib\…)
  • The “Library path” field contains:
    • \$ (BCB)\lib
    • \$ (BCB)\lib\obj
  • “Intermediate output” = “obj”
  • “Final output” = “..\..\..\..\prog\” - This defines where your executable will be created. If you want it elsewhere, just change the path, knowing that it is relative.
  • “Conditional defines” = “MODTYPE=2” - The number is the type of module (1 for acquisition, 2 for signal processing, 3 for application)

You can now try compiling your project. Then open the BCI2000 launcher again and move your module from the “Other” box into the “Signal Processing” box. Repeat the same steps mentioned in the previous chapter.

The filters

Now that you have the signal processing module ready, you have to create your own filters. Start by creating a new unit (“File”“New”“Unit”). Save it right away to change it's name.

Header

Open the header (there is a thumbnail at the bottom of the window).

Include “GenericFilter.h” and define your filter as a class inheriting from “GenericFilter” (class YourFilter : public GenericFilter). This class is inherited to be sure you have implemented all necessary methods to fit the BCI2000 protocol.

The filter requires three public methods (apart from the constructor and destructor):

  • virtual void Preflight( const SignalProperties&, SignalProperties& ) const;
  • virtual void Initialize( const SignalProperties&, const SignalProperties& );
  • virtual void Process( const GenericSignal& Input, GenericSignal& Output );

These methods are part of the BCI2000 protocol. They will be called in turn when you start the whole process, the Preflight and Initialize methods when you set the configuration, the Process method for each sample block while the application runs.

You may also want to implement some optional methods of the GenericFilter class. For more information about these, check the wiki.

C++ File includes and declarations

Now switch back to the filter file (.cpp). Include “PCHIncludes.h” and any other file you might need (for those, you should open the “src/shared” folder).

Keep #pragma hdrstop right after the inclusion of “PCHIncludes.h” but delete the other #pragma. Again, this is a precompiler command. You will need “using namespace std;” to activate i/o.

Then call “RegisterFilter” with the name of your filter and its default rank: “RegisterFilter(, 2.C)” for instance. You will be able to place your filter anywhere afterwards, so do not spend time thinking about the default rank.

If you have any parameters to your filter, you will have to declare them in the constructor. They will then appear in the configuration window under the specified thumbnail and section. This is done by writing “BEGIN_PARAMETER_DEFINITIONS” and “END_PARAMETER_DEFINITIONS”

in the body of the constructor. Parameters are then declared between these two instructions and following this protocol:

Section type name= value default Min Max // Comment

This can change if the type is a list or matrix. Everything is explained in detail on the wiki. The section should be Filtering, or maybe Visualize if the parameter only changes the visual output. This serves to place the parameter in the right thumbnail in the configuration interface of the operator.

Each parameter must be in between quotes and separated by a coma, even the last one before “END_PARAMETER_DEFINITIONS”.

For instance:

BEGIN_PARAMETER_DEFINITIONS
	"Filtering int frequency= 18 18 5 50"
		"// the frequency of something in your filter",
	
	"Filtering int box= 0 0 0 1"
		"//this is a boolean parameter, it makes a checkbox because of the boundaries that allow only 0 or 1",
END_PARAMETER_DEFINITIONS

State variables, which are used somewhat like global variables for all three modules, are defined in the same way, if you need some. More details are on the wiki.

Necessary methods

Then comes the preflight method. It is called when you set the configuration. Its purpose is mainly to check the different parameters. If you have bound conditions, you should check them by using the Parameter function with the name of the parameter. For instance: “Parameter(“frequency”);” You can also write special conditions, such as dependencies between two parameters. States are also checked that way, but with the State function. The last thing you should check (if it is important) is the correspondence between the input and the output properties. This is done easily: “OutputProperties = InputProperties”, InputProperties and OutputProperties being the first and second parameter of the Preflight method.

The Initialize method is called once at the beginning, just after the preflight method. Any variable you need to initialize should be treated here.

Finally, the Process method is the core of your filter. This method is called for each signal block. That means you do not need to create a loop to work on the different blocks. Input is a two-dimensionnal matrix. The first dimension represents the channels, the second the samples. The size of the second dimension thus depends on the size of blocks you transmit. For instance, if you transmit 4 channels at 32 samples/block, you will get a 4×32 matrix as Input for each call of Preflight.

Included filters

This short list of filters may prove useful. Be sure to check your own list of filters, as some may have been added.

  • SpatialFilter - Applies a linear transformation to the signal.
  • FFTFilter - Does a Fast Fourier Transform on the signal.
  • Normalizer - Can modify the gain and offset of the signal. Can also modify the signal to have a mean of zero.
  • RandomFilter - Multiplies the signal by random zero-mean noise and outputs the result to extra channels.
  • LPFilter - A simple low-pass filter.

For more details on each filter, check the BCI2000 wiki.

There are other filters available. Just open the “src/shared/modules/signalprocessing/” folder and open them in Borland C++ Builder to see what they are about (there is a comment at the top of the code that explains the filter's use).

previous step next step

back