Vous êtes sur la page 1sur 100

Programming Howto:Quickstart Guide

This tutorial walks you through the process of obtaining the BCI2000 source distribution, and using it to
build and test your own custom filters, implemented in C++ inside your own custom core module. It
assumes that you have a good working knowledge of the C++ language, and basic familiarity with the
compiler/IDE that you are going to use.

Many of the specific instructions below will assume that you are on a 32-bit Windows system, are using
Microsoft's free Visual C++ 2010 Express compiler, and have checked out the BCI2000 distribution to a
location C:\BCI2000\3.x on your hard-drive. However, the same steps are valid for other supported
setups, with the appropriate setup-specific changes.

Contents
[hide]

 1 Prerequisites

o 1.1 All Operating Systems

o 1.2 Prerequisites (Linux)

o 1.3 Prerequisites (OS X)

 2 Setup

 3 Creating a project for a new BCI2000 module, from a template

 4 Adding a new filter to a BCI2000 module, from a template

 5 Running and testing the result

 6 Configuring your new filter for offline use

 7 Exercises

o 7.1 Exercise 1: RMSFilter

o 7.2 Exercise 2: Debugging

o 7.3 Exercise 3: Assembling a filter chain

o 7.4 Exercise 4: Offline filter-chain reconstruction in Matlab

 8 Further reading

Prerequisites

All Operating Systems

1. First, you need a complete BCI2000 distribution that includes the src and build directories. If
you already have this, use SVN to make sure it is updated to a recent stable version (at least, for
this tutorial to make sense, you will need revision 3279 or later / May 2011 or later). If you do not
already have the source distribution, here is how you obtain it:

1. If you do not already have a bci2000.org username, get one here. Your password will be
mailed to you.
2. To download the BCI2000 source-code, you will need an SVN client. If you do not have
one, for Windows we recommend TortoiseSVN, which can be downloadedhere. The
BCI2000 wiki contains more documentation about how to set up and use SVN.

3. Use SVN, with your username and password, to check out the latest BCI2000 source-
code-included distribution, the location of which
ishttp://www.bci2000.org/svn/tags/releases/current (note this is the current release
version, not the current source code -- for the current source code, check out
from http://www.bci2000.org/svn/trunk). We will assume that you checked out to a
location C:\BCI2000\3.x on your hard-drive. Wherever you see this path below,
adjust it so that it reflects the location you actually used. More information about the
layout of the resulting distribution can be found here.

2. Next you will need to download and install CMake if you do not already have it (at least version
2.8.3 is recommended). CMake can be downloaded here. Make sure you select the option add to
PATH for all users when installing.

3. Finally you will need a C++ compiler. We will proceed on the assumption that you are on 32-bit
Windows and using the free Visual C++ 2010 Express environment which can be
downloaded here (choose either the Visual C++ web-installer or, for something that can be
installed offline, the option marked "Visual Studio 2010 Express All-in-One ISO" - this will give you
an .iso file which can be mounted as a virtual drive using a tool like Virtual Clone Drive). Consult
the documentation for the BCI2000 build system for information on other supported compilers,
and more about CMake.

You should now have everything you need in order to build and customize BCI2000.

Prerequisites (Linux)

1. C++ compiler

2. Subversion

3. CMake

4. Qt libraries and header files (Qt-dev)

Then follow the "unix command-line equivalent" instructions from a terminal.

Prerequisites (OS X)

1. Apple's Developer Tools (includes a C/C++ compiler and Subversion)


2. A CMake binary installer (.dmg file) appropriate to your platform, version 2.8.3 or later.

3. Qt libraries (You only need the libraries, not the full SDK. We have performed successful builds
with v4.7.2. Your mileage may vary according to the version you use.)

Then follow the "unix command-line equivalent" instructions from the Terminal.app command-line.
NOTE: CMake may be used to create an XCode project file for BCI2000, using the appropriate generator
option. However, it seems that XCode builds of BCI2000 do not always succeed, even if there is no
problem with Unix makefile builds on the same machine. Thus, you should always try a command-line build
when building fails under XCode.

Unix command-line equivalent:

$ cd ~ # or wherever
$ svn checkout http://www.bci2000.org/svn/trunk bci2000 # (or call it
whatever you want)

Setup

1. Use CMake to build a project file. This is done by going to C:\BCI2000\3.x\build (which is
the main workbench from which all build operations occur) and launching one of the " Make ...
.bat" batch-files. If you are using Visual C++ 2010 Express, the appropriate batch file is Make
VS10 Project Files.bat . It may ask you several questions: feel free to answer "y" to
building the "tools", "contrib" and "BCPy2000" components. For now, for simplicity, you may wish
to say no to framework extensions. Also say no to modules that require MFC (unless you are
using a commercial version of Visual C++ Studio that includes MFC). If successful, you should
see a long list of sub-projects being created, and after several seconds of this, "Configuring done"
followed by "Generating done".
2. Open the resulting solution file, BCI2000.sln , in Visual C++ Express. The file should be located
in C:\BCI2000\3.x\build .

3. Ensure that you are in "Release" mode rather than "Debug" (in Visual C++, this is a drop-down
menu in a toolbar near the top)

4. You probably do not need to build all of BCI2000 at once. To use BCI2000, the minimum you will
need consists of the Operator, plus at least one SignalSource module, at least one
SignalProcessing module, and at least one Application module. To build a single module in Visual
C++ Express, right-click on the module's name in the list on the left, and select "Build". The initial
build will typically take a few minutes per module. If you do not intend to modify a given module,
you can always skip building it by copying the corresponding ready-built binary (for
example, Operator.exe) from the prog subdirectory of a BCI2000 v.2 or v.3 binary
distribution, and paste it into the corresponding directory of the distribution in which you are
working, i.e. C:\BCI2000\3.x\prog . (To download the latest binary distribution, you will need
to supply the same username and password that you used for SVN download.) Let's assume you
have built, or copied, the following
modules: SignalGenerator, ARSignalProcessing,CursorTask and Operator, as well as the
necessary OperatorLib shared library.
5. Build the NewBCI2000Module, NewBCI2000Filter and NewBCI2000FilterTool targets for use in
the subsequent steps. The result will be new
executables,NewBCI2000Module.exe, NewBCI2000Filter.exe and NewBCI2000Filte
rTool.exe, in the C:\BCI2000\3.x\build directory.

Unix command-line equivalent:

$ cd ~/bci2000 # or wherever
$ cd build
$ ./Make\ Unix\ Makefiles.sh
$ make NewBCI2000Module NewBCI2000Filter NewBCI2000FilterTool

Creating a project for a new BCI2000 module, from a template

1. Navigate to C:\BCI2000\3.x\build

2. Find NewBCI2000Module.exe there, assuming you have already built it (see "Setup", above).

3. Launch it and answer the three questions it asks:

1. What type of module are you creating? Enter "1" for a SignalSource module, "2" for a
SignalProcessing module, or "3" for an Application module.

2. What should be the name of the new module? This should be single word, without spaces
or punctuation (For example: VeryNiceSignalProcessing. Or, at your option, something
even more informative.) This name will be used as the name of a new directory that will
contain the project source information, as well as for the resulting binary (.exe file).

3. Where (i.e. inside what parent directory) should the new project directory be created? You
can express this as a relative path, in which case it is interpreted relative to
the build directory where NewBCI2000Module.exe itself is running. To accept the
default answer, which is ..\src\custom, just press return. This location (or some
subdirectory of it) is a good choice: it maps to C:\BCI2000\3.x\src\custom which
is an area reserved for your own projects.

4. Now re-run CMake (see step 1 of "Setup", above). You should see your new project being added
at the end of the long list of projects. Re-open the project file (step 2 of "Setup") and you should
see it listed in the alphabetical list of projects.

5. If you have just created a new SignalSource project, a specialized source-acquisition filter (called
an Analogue-to-Digital Converter or ADC) will already have been added to the project. If your
project was called Foo or FooSource this will be instantiated in files
called FooADC.cpp and FooADC.h. You can find this by expanding the tree (click on the plus
sign) next to your project name in Visual C++. Find the files under "Source" (or "Headers") and
then "Project". Double-click them to edit. You should already be able to compile the module: try it.
6. If you have created a SignalProcessing or Application project, you may be able to compile it, but it
will not do anything until you add at least one filter to it (see next section).

Unix command-line equivalent:

$ cd ~/bci2000 # or wherever
$ cd build
$ ./NewBCI2000Module 2 VeryNiceSignalProcessing ../src/custom
$ ./Make\ Unix\ Makefiles.sh
$ make VeryNiceSignalProcessing

Adding a new filter to a BCI2000 module, from a template

1. Navigate to C:\BCI2000\3.x\build

2. Find NewBCI2000Filter.exe there, assuming you have already built it (see "Setup", above).

3. Launch it and answer the three questions it asks:

1. What type of filter are you creating? Enter "1" for a subclass of BufferedADC (although if
you created a SignalSource project with the NewBCI2000Module tool, an ADC of this
kind will already have been created for you), enter "2" for a subclass of GenericFilter, or
"3" for a subclass of ApplicationBase.

2. What should be the name of the filter class? This should be a legal name for a new C++
class (no spaces or punctuation). If you enter, for example, "FooFilter", then a class of
this name will be implemented in two new files, FooFilter.cpp and FooFilter.h .

3. To which project (i.e. in which project directory, relative to build) should the filter be
added? For example: ..\src\custom\VeryNiceSignalProcessing (as in all of
the BCI2000 framework, directory slashes are allowed to go this / way or that \ way
regardless of whether you are on Windows or not)

4. Again, re-run CMake and re-open the master project file.

5. Find your new files under the Source/Project and Headers/Project trees by clicking on the + sign
next to the project name. Double-click them to edit. Read the comments in the file for help as to
how to flesh out the various filter methods.

6. Try building the project (right-click on the project and select "build"). SignalSource and Application
projects may already build successfully. SignalProcessing projects, or any project to which you
have added a new GenericFilter subclass, will have at least one deliberate #error in the code.
When the build attempt finishes, double-click on the error message to go to the offending line.
Read the comments in the file, and you will see that the error is there to force you to think about
the ordering of the filters in your module. Once you have resolved this issue, simply remove the
#error line and select "build" again (the process will be quicker this time: only previously unbuilt
files or newly modified files will be compiled).

Unix command-line equivalent:

$ cd ~/bci2000 # or wherever
$ cd build
$ ./NewBCI2000Filter 2 FooFilter ../src/custom/VeryNiceSignalProcessing
$ ./Make\ Unix\ Makefiles.sh
$ make VeryNiceSignalProcessing

Running and testing the result

1. Locate successfully built modules, which will appear as .exe files inside the top-
level prog directory of the BCI2000 distribution, for example C:\BCI2000\3.x\prog

2. The most basic way to launch BCI2000 is to double-click on each module in turn. Start
with Operator.exe, then launch one SignalSource module, one SignalProcessing module, and
one Application module (your new module will fill one of these three roles, but all three are
required).

3. If you have a firewall running on your machine, dialogs may open for any modules that the firewall
has never seen before. If so, click "unblock" to proceed, for each one. You should only ever need
to do this once per newly-built module. (Check that your firewall is not configured to block network
connections without telling you.)

4. In the Config dialog, under the Visualize tab, you should see a check-box for visualizing the
output of all the filters in the chain, including your newly created filter. You may find it useful,
whenever you are developing a new filter, to visualize your filter input and output simultaneously
(i.e. visualize both the new filter and the filter that immediately precedes it in the chain).
Configuring your new filter for offline use

This is an optional step which many developers of new custom filters will find useful. Filters may be
compiled singly as standalone executables to allow them to be tested and used for data analysis offline.
The resulting "filter tools" can be used either from the DOS/Unix command-line or, more comfortably, from
the Matlab command-line.

1. Navigate to C:\BCI2000\3.x\build

2. Find NewBCI2000FilterTool.exe there, assuming you have already built it (see "Setup",
above).
3. Launch it, and tell it which existing C++ file contains the filter definition (as always, specify the path
relative to the build directory—for
example,..\src\custom\VeryNiceSignalProcessing\FooFilter.cpp)

4. The tool will perform for you the necessary alterations to the CMakeLists files. (The procedure,
details of which can be found here, involves creating a subdirectory calledcmdline inside your
project directory, because specifying a new executable target in the same CMakeLists.txt file
as your module would cause a misconfiguration problems for either the module or the filter-tool. )
5. Close BCI2000.sln, re-run CMake, and re-open BCI2000.sln. You should now have a new
target, whose name is simply the name of the filter (e.g. FooFilter).

6. Right-click on the new target and select "build". The resulting binary will appear as (for
example) ..\tools\cmdline\FooFilter.exe

7. If you have Matlab, consult User_Reference:Matlab_Tools to see how to proceed. Otherwise


see User_Reference:Command_Line_Processing. (In the Matlab version, the "Hello World"
messages of the default template filter, and any other bciout debugging outputs you specify, will
appear in the .ShellOutput field.)

Unix command-line equivalent:

$ cd ~/bci2000 # or wherever
$ cd build
$ ./NewBCI2000FilterTool
../src/custom/VeryNiceSignalProcessing/FooFilter.cpp
$ ./Make\ Unix\ Makefiles.sh
$ make FooFilter
$ ../tools/cmdline/FooFilter --help
$ make bci_dat2stream bci_stream2mat # you'll also need these

Exercises
Exercise 1: RMSFilter

1. Use NewBCI2000Module to create a new SignalProcessing module.

2. Use NewBCI2000Filter to create a new filter called RMSFilter in your new module.

3. NewBCI2000Module and NewBCI2000Filter report that they are creating and altering various files
and directories. Write down what you think is the purpose of each new directory, new file, or
alteration to an existing file.

4. Write a filter called RMSFilter, which takes in multiple signal channels, and outputs a single
channel containing the root-mean-square signal across all input channels. (Advanced variant:
introduce a parameter which allows the user to specify groups of channels; then output one RMS
signal per group.) Test the filter using theSignalGenerator module as an input, configuring the
SignalGenerator to respond to mouse movement as described in the documentation. First
visualize just the input of your new filter (the output of the previous filter, which will presumably be
the TransmissionFilter). Then draw on paper what you would expect to see as an output of your
filter in response to different mouse positions/actions. Finally, visualize your filter's output in order
to verify that it matches your expectations.

5. Visualization allows you to test your code's behaviour qualitatively to some extent. In what various
different ways could you verify, in a more precise, quantitative way, that your implementation is
correct? Under what circumstances should you spend the extra time to do this? (hint: the answer
rhymes with "hallways")
Exercise 2: Debugging

1. Write a batch file to launch your combination of modules. You may wish to use one of the existing
files in the top-level batch directory as a template. A batch file will allow you to go around each
edit-compile-debug cycle much faster and more reliably. It will also allow you to pass useful
command-line flags to the modules as you start them. And finally, if you need to set parameters in
a certain way on each launch, we strongly recommended that you take advantage of operator
scripting in your batch file to ensure that the required parameters are loaded automatically from a
file on each launch. Inconsistent behaviour from one debug cycle to the next can often be
attributed to having forgotten to perform the menial task of loading parameters manually.

2. You can also use the Visual C++ debugger as follows:

1. In your batch file, comment out or remove the line that launches the module you want to
debug.

2. Again in your batch file, assuming you are using the SignalGenerator, FilePlayback, or
some other source module which does not have to run in real-time, add the flag --
EvaluateTiming=0 to the call that launches the source module (see example snippet
below). Putting a debug breakpoint in your Process() method will slow the system to
below real-time, and we do not want the framework's real-time check to terminate the
debug session for this reason.

3. Launch the batch file, thereby launching all the necessary modules except one, and
loading any parameters needed for the debug session.

4. Note that there are different "build modes" for the BCI2000 solution, with names like
"Release" and "Debug". In order to debug a particular module, you will need to ensure
that the module is built in either "Debug" mode or "RelWithDebInfo" mode. If you are in
the wrong mode for debugging, select the correct build mode from the drop-down menu
on Visual C++ Express's toolbar, then build your module again. Note that the BCPy2000
modules (PythonSource, PythonSignalProcessing andPythonApplication) cannot be built
in Debug mode. Also, Debug mode can sometimes mask dangerous bugs, so you may
experience the frustration of trying to investigate a crash only for it to stop happening
when you switch from Release to Debug. Finally, a BCI2000 module built in Debug mode
may exhibit poorer timing performance. For these reasons it is not generally advisable to
use Debug mode as the default mode of your project, and RelWithDebInfo is often
advisable when debugging (even though the compiler optimizations may lead to a less
logical-seeming debugging experience).

5. In Visual C++, set a breakpoint in the source file you want to debug.

6. Although CMake directs Visual C++ to create the module in the correct
directory, C:\BCI2000\3.x\prog, it is unable to set the working directory in which
the debug instance runs to the same value. Therefore, if your module needs to load
resources like image or sound files, and expects to find these at the end of a path that is
expressed relative to prog, you will need to set the prog directory as the module's
working directory by hand: Right-click on the module -> Properties -> Configuration
Properties -> Debugging -> Working Directory

7. In Visual C++, right-click on the module, then select "Debug" followed by "Start New
Instance" (if you find this too fiddly to do with the mouse, right-click, then press "g", then
press "s" ). If the build is not up-to-date, Visual Studio will prompt you: in this case, allow
it to build the module.

8. You are now debugging BCI2000. Verify that the debugger stops your module at the
breakpoint you specified and at the time that you expected. Browse the available
variables and their properties.

start SignalGenerator.exe --LogMouse=1 --EvaluateTiming=0

Exercise 3: Assembling a filter chain

1. Write a filter called DiffFilter, in which each output channel contains the numerical derivative, with
respect to time, of the corresponding input channel. Note that your Process() method only sees
one discrete chunk (or SampleBlock) of signal at a time. Is this a problem? Use private member
variables of your filter instance as a "scratch-pad" where necessary.

2. Assemble the pre-existing ExpressionFilter, followed by your DiffFilter, followed by your RMSFilter
(from exercise 1), in that order within one SignalProcessing module. The ExpressionFilter
implementation is already part of the framework, so all you need to do is uncomment
the #include and Filter() statements that correspond to it inPipeDefinition.cpp.
Read the comments in PipeDefinition.cpp, and
the Programming_Reference:Filter_Chain wiki page, for more information about linking filters in a
chain.
3. Make sure your SignalSource module is started with the --LogMouse=1 flag (see the code
snippet above).
4. Set the Expressions parameter (found in the Filtering tab of the Config dialog) such that it has
two rows and one column, and contains the expressions "MousePosX" and "MousePosY".

5. Write down a full description of how you think the SignalProcessing module will process the signal,
from start to finish, and exactly how you expect it to respond to different kinds of mouse
movement.

6. Verify that the module behaves the way you expect, under all relevant input conditions.
Exercise 4: Offline filter-chain reconstruction in Matlab

1. Take one or more of the filters you have written (RMSFilter, DiffFilter), or create a new one which
also does something whose numerical output can be checked very easily (examples: squaring the
input signal, or doubling it, or taking the absolute value).

2. Use NewBCI2000FilterTool to create a command-line "filter tool" executable as described above.


3. Also ensure that the bci_dat2stream and bci_stream2mat targets have been built.

4. Learn how to use the matlab function bci2000chain and the supporting Matlab tools.
5. Use bci2000chain to run your filter on some example data, or even on some toy data that you
create using create_bcidat. Then, separately, write a Matlab function for performing the same
numerical operation that you believe your filter performs. Compare the two outputs and verify that
your filter does exactly what it is intended to do, at least to within some very small numerical
tolerance. The maximum absolute difference between the two outputs should be very small (say,
1e-10 or less, depending on the operations and on the magnitude of the input signal).

Programming Reference:Build System


(Redirected from Programming Howto:Building BCI2000)

Contents
[hide]

 1 Introduction

 2 Supported Environments

 3 Setting up MinGW to build BCI2000

 4 How To Build Using CMake

 5 Building against a Qt distribution outside the BCI2000 source tree

 6 How To Test

 7 Known Issues

 8 Conclusions

 9 Windows platforms tested successfully so far

 10 Status on other OSes

o 10.1 Linux
o 10.2 OS X

 11 See also

Introduction

Previous versions of BCI2000 (2.x and earlier) were dependent on the visual components library (VCL) by
Borland and could only be built using the Borland compiler. For version 3.0, we made BCI2000 compiler-
independent, and therefore had to choose a portable replacement for VCL. Qt was chosen to replace VCL
because it is not only compiler-independent, but also platform-independent.

To support such a large number of platforms and compilers, BCI2000 is using


CMake http://www.cmake.org to generate Makefiles, project files, and Visual Studio solutions. CMake can
be thought of as a meta-makefile; it examines your build environment and sets up a Visual Studio project, a
Code::Blocks project, or a Unix Makefile which is custom tailored to your environment. Due to the wide
variation in possible build environments, BCI2000 can not come with a fixed Visual Studio solution file, or a
fixed Eclipse project file (such fixed solutions would always end up costing users a lot of effort). Thus, the
number of platforms BCI2000 supports is mainly limited by the number of platforms and compilers for which
CMake has generators.

Supported Environments

 Supported Operating Systems

 Windows XP, Windows 2000, Windows 7 (Vista not recommended)

 64-bit Windows systems

 Macintosh OSX: Compiles and passes standard tests on 10.5 and newer systems

 Linux: Compiles and passes standard tests on Wheezy (currently stable)

 Supported Compilers

 Visual Studio 9 (2008) and 10, download free Express version from here (although some BCI2000
modules require MFC, most modules may be built using the Express version)

 MinGW with gcc 4.x

 Supported IDEs

 Qt Creator

 Visual Studio 9 (2008), Visual Studio 10

 Code::Blocks - MinGW Makefiles

 Other IDEs supported by CMake generators (Eclipse CDT, ...), as long as these use the compilers
listed above
Setting up MinGW to build BCI2000

This section only applies when you want to use MinGW as a compiler to build BCI2000. If you are using a
different compiler/IDE, please proceed to the next section.
 If you don't care whether your BCI2000 executables are statically linked against Qt libraries, you may
use any recent version of MinGW. You will need to install Qt separately from BCI2000, and you will
need to follow the instructions in the next section, "Building against a Qt distribution outside the
BCI2000 source tree".

 If you want to statically link BCI2000 against Qt libraries using any version of MinGW, you will need to
install Qt separately from BCI2000, and recompile it from source with the "static" configuration flag as
described here. Then, follow the instructions in the next section, "Building against a Qt distribution
outside the BCI2000 source tree".

 If you want to use the pre-compiled static Qt libraries in the BCI2000 source tree, you will need to
install a compatible version of MinGW. Currently, this is MinGW with gcc 4.4.0, which can be
downloaded from [1]. Extract this file into any path on your local hard drive, and add its "bin"
subdirectory to your system path variable so MinGW commands are recognized when entering them
from the command line.
How To Build Using CMake

1. Ensure that your compiler and IDE are installed on the computer. This means that Visual Studio is
installed if you intend to use Visual Studio, or that MinGW and Code::Blocks are installed if you
intend to use Code::Blocks as your IDE and MinGW as your compiler.

2. Download and install cmake (Version 2.8.2 or higher!) from http://www.cmake.org/ - Add to path
for all users. -- If you experience a problem with a version of cmake newer than 2.8.3, download
cmake 2.8.3 from here.

3. Go to the BCI2000/build directory and double-click the "Make ... .bat" file which best describes
your intended platform. (for example, if you plan to use Visual Studio 2008, you would run the
"Make VS2008 Project Files.bat"). These batch files will ask you a few questions about which
parts of the BCI2000 distribution you want to make, and will then call CMake with the appropriate
options.

4. Wait while cmake examines your computer, finds Qt and your compiler, and generates applicable
project files for your system

5. Open the generated project/solution (for Visual Studio, this is called "BCI2000.sln") in the IDE of
your choice. Or, if you are using MinGW make, run the make command from the command
prompt using the Makefile in the top-level "build" directory. Refer to your IDE's help for IDE-
specific instructions on how to build an application.

6. Even though CMake must always be run from one top-level place (the "build" directory), and will
spend several seconds generating a Makefile or project file for the whole BCI2000 tree at once,
this does not mean that you have to compile all of BCI2000 at once. In the next step, if you're
using MinGW-make on the command-line, you could for example type "make Operator" or "make
SignalGenerator" to build only selected modules (again, you should be in the top-level "build"
directory when you do this). Visual IDEs usually have their own equivalent of this: for example, in
Visual Studio 2008, you can right-click on a particular module and select "Build".

7. If you build the targets"NewBCI2000Module" and "NewBCI2000Filter", this will create two new
binary executables with these names. They will be found in the top-level "build" directory (and, in
common with CMake and make, they should be launched only from this location). Launch
NewBCI2000Module to create a new project in order to compile your own custom module. Launch
NewBCI2000Filter to create a new filter and add it to a project you have already created. In either
case, you may need to re-run CMake (step 3) in order to ensure these changes are reflected in
the project file.
Building against a Qt distribution outside the BCI2000 source tree

BCI2000 comes with a stripped-down, precompiled version of Qt, which is downloaded by CMake at
configuration time. This is a static version of the Qt libraries, and allows to distribute the resulting
executable without hindsight to DLL dependencies. The complex and annoying implications of such
dependencies has been termed "DLL hell" for good reasons, so we strongly recommend linking against the
BCI2000-specific Qt libraries for Windows builds.

When running CMake, it determines the current compiler/OS combination, and tries to download an
appropriate version of the BCI2000-specific Qt build. If no such build is available, it expects a local Qt
installation to be available, and fails with an error message if this is not the case.

If a BCI2000-specific Qt build is available for the current configuration, you will still have the option of using
a locally installed version of Qt. This is a CMake optionUSE_EXTERNAL_QT appearing in
the CMakeCache.txt file after CMake has been run for the first time. Such options may be changed
using the CMake GUI, or a text editor. After changing the option, run CMake again to apply the change to
the created project files.

How To Test

It is important - especially if you're using an unsupported compiler/IDE - to test your executables once
they've been built to make sure they function properly.

To run testing:

1. Ensure that the entire BCI2000 project has built successfully and that executables exist in
the BCI2000/prog directory, the BCI2000/tools/cmdline directory, and
the BCI2000/build/buildutils/test directory.

2. Run TestExecutables.bat in the BCI2000/build/buildutils/test directory. (This is


a cross-platform script, and may be run on non-Windows platforms as well, provided that it is run
from the directory in which it resides.)
The tests will run and report a message after they've finished whether they've failed or not. You may see a
message in the beginning of the test stating that a directory does not exist. This is normal behavior and
does not reflect whether or not your executables have failed testing or not.

A Note on found differences: differences may not indicate that you have a broken build. Sometimes
different compilers handle the precision of floating point numbers differently than others. These can
account for small differences in calculated signal or state values. The default reference files were
generated using an MSVC build on 32 bit Windows. If your compiler is different, this may not be a problem.

Known Issues

 MinGW, Borland and other single configuration generators within CMake only generate one
configuration at CMake Run-time. By default, this is set to the release configuration. It can be set -
along with specific compiler options -
in BCI2000/build/cmake/BuildConfigurations.cmake. The Visual Studio generator will
ignore settings in this file. To turn on a debug build in a single configuration generator, run cmake -
i in the build directory and set CMAKE_BUILD_TYPE to "Debug" when prompted.

 All Compilers handle non standard characters, such as umlauts and characters with accents or tildes,
differently. Because BCI2000 currently has no standardized way of handling non standard characters
in a cross-compiler environment, it is strongly recommended that - for the time being - special
characters are not used in localizations during the development of BCI2000 Ver 3.0.
Conclusions

Now that BCI2000 is open to a number of platforms, and compilers, support may not exist for every
possible compiler/platform available. Certain compilers do not optimize code as well as others, and this
behavior may lead to poor system latencies during BCI2000 experiments. The supported compilers have
been rigorously tested and confirmed to be adequate for compiling the BCI2000 sources. If you wish to use
a different compiler, be sure to run tools/BCI2000Certification in order to confirm your setup. CMake is a
powerful tool, but in the end, ability to compile the sources is completely up to the IDE/compiler choice. If
your IDE/compiler choice is not listed above, it is strongly urged that you to consider using one which is
supported. If you run into problems using an unsupported IDE/compiler combination, you can try to find
help at the BBS -http://www.bci2000.org/phpbb/index.php. BCI2000 should compile as effortlessly as
possible on supported platforms.

Windows platforms tested successfully so far


Compiler OS Processors Qt linkage
MSVC2008 Win XP SP3 2 static
MSVC2008 Win XP SP3 1 static
MSVC2008 Win 7 32bit 1 static
MSVC2008 Win 7 64bit 1 static
MSVC2010 Win XP SP3 1 static
MSVC2010 Win XP SP3 1 dynamic
MinGW > 4 Win XP SP3 1 static
MinGW > 4 Win XP SP3 1 dynamic

Status on other OSes

Note that the Qt libraries provided in the BCI2000 source tree are for Windows only, so you need to
separately install Qt on your system before compiling BCI2000.

Linux
Executable tests are passed on x86 and amd64 architectures running Debian Squeeze (currently "Stable")
and Wheezy (currently "Testing").

OS X
BCI2000 builds successfully in OS X Leopard and Snow Leopard using the CMake generating script
at build/Make Unix Makefiles.sh. Executable tests run successfully on OSX, both in 32 and 64 bit
mode.

Programming Reference:Filter Chain


Contents
[hide]

 1 The Filter Chain

 2 Filter Instantiation

 3 Signal Processing Filter Instantiation

 4 Recreating Filter Chains Offline

The Filter Chain

As noted in the discussion of the GenericFilter::Process function,


all GenericFilter descendants inside a BCI2000 module form a single chain of filters. Each filter's
output forms the input of the subsequent filter.

For each module, the filter chain is documented in its own FilterChain parameter, which is located in
the System/Configuration section.

Filter Instantiation

Creating instances of all filter classes inside a module, and building the filter chain, is handled by the
framework. However, it needs a hint to determine the sequence in which filters are to be arranged. In
general, this hint consists of a single statement placed inside your filter's .cpp file:

RegisterFilter( MyFilter, 2.C );


The first argument to this statement is the class name of your filter; the second argument is a string value
(given without quotes) that determines the relative position of your filter in the filter chain. This is done by
applying the simple rule that the filter positions in the chain match the alphanumeric sorting order of the
associated postion strings of the filters. This scheme allows you to place an additional filter between
existing ones without changing the position strings of the existing filters.

As a rule, use position strings beginning with

 1. for filters that go into source modules,

 2. for filters from signal processing modules,

 3. for filters from application modules.


Signal Processing Filter Instantiation

In principle, the above scheme allows to you add filters to a module's filter chain without modification to the
existing source code, simply by adding a .cpp file with aRegisterFilter statement to the project.
However for Signal Processing modules, it is more desirable to have an explicit representation of the entire
filter chain centralized in one place. So, for each individual Signal Processing module there is one
file PipeDefinition.cpp that defines the filter chain as a sequence of Filter statements
(seeProgramming Tutorial:Implementing a Signal Processing Filter for an example). In other modules,
filters' default position will be suitable in general. Still, you may add aPipeDefinition.cpp with a
sequence of Filter statements to the module. In this case, positions defined
in PipeDefinition.cpp will take precedence over the filters' default positions.

Recreating Filter Chains Offline

The BCI2000 command-line tools can be used to recreate a filter-chain offline, from a system command-
line. The Matlab function bci2000chain, part of BCI2000's suite of Matlab tools, provides a convenient
interface to this from within Matlab.

Programming Reference:Class Hierarchies


BCI2000 consists of a large number of classes, which interact in complicated ways, and are difficult to
oversee. Most of these classes are of little importance to the overall picture of BCI2000. In fact, knowledge
of a small number of BCI2000 classes, and their inheritance relationships, is sufficient to understand how
BCI2000 works, and how to write your own BCI2000 filters and modules.

This page gives an overview over the class hierarchies which matter to you when doing programming work
in BCI2000. It does not describe individual self-contained classes such as
the SignalProperties or GenericSignal; rather, it explains classes that are part of hierarchies, each playing a
certain role in its hierarchy.

Contents
[hide]
 1 Types of Classes

o 1.1 Interface Classes

o 1.2 Mix-in Classes

o 1.3 Client Classes

 2 Class Hierarchies

o 2.1 GenericFilter

o 2.2 EnvironmentExtension

o 2.3 ApplicationBase

o 2.4 GraphObject and GraphDisplay

o 2.5 Stimulus

o 2.6 Target, SpellerTarget and Speller

 3 See Also

Types of Classes

In BCI2000, class hierarchies consist of three types of classes: Interface class, mix-in classes, and client
classes.

Interface Classes
Interface classes, which provide an abstract interface for functionality that is then implemented by classes
derived from those interface classes. Due to the common interface, the BCI2000 framework can deal with a
great number of derived classes, serving their needs without even knowing which derived classes exist and
what they do.

Interface classes typically declare a number of so-called virtual functions. In the interface class, these
virtual functions do nothing specific; in most cases, they are not even defined, such that a derived class is
forced to implement its own version of that function. An interface class' virtual functions provide an interface
to their derived classes. In addition to virtual functions, an interface class also has plain member functions;
these provide an interface to classes that use objects of the interface class, rather than inheriting from it.

In BCI2000, there are a large number of interface classes that represent programming interfaces for filters,
data acquisition interfaces, file writers, application modules, and graphic objects. The most important
interface class is the GenericFilter class, which defines the programming interface for all BCI2000 filter
components that are part of the filter chain.

Most often, interfaces are built around the notion of an "event". When an event happens, it requires an
action. Actions are defined by client classes that implement an interface. Each virtual function in the
interface corresponds to an event, and derived classes implement those functions, thus providing event
"handlers". As an example, the GraphObjectinterface class declares a virtual function called OnPaint().
Individual graphic objects inherit from the GraphObject class, and implement the associated interface by
providing their own OnPaint() function. Whenever a window needs redrawing, graphic objects are asked to
handle the Paint event, i.e. for all graphic objects tied to the respective window, theOnPaint() event handler
is called. In its OnPaint() function, a GraphObject descendant class that represents a circle will provide
code that draws a circle, while a GraphObjectdescendant representing a text field will implement
an OnPaint() event handler that draws text.

Most often, classes directly inherit from a single interface class only, e.g. when you write a BCI2000 filter
component, it inherits from the GenericFilter interface class. However, it is not uncommon that there is a
hierarchy of interface classes which all build upon each other, where the line of inheritage represents
increasing specialization. As an example, all BCI2000 filters inherit from the GenericFilter interface class.
However, data acquisition filters do not inherit from GenericFilter directly; rather, they inherit from a class
calledGenericADC, which in turn inherits from GenericFilter, i.e. it specializes the GenericFilter interface for
data acquisition components. In addition, there exists an interface class called BufferedADC which
provides buffering, and an interface for data acquisition components that write their acquired data into a
ring buffer. A class that inherits the BufferedADCinterface thus implements an interface that has been
specialized all the way down from GenericADC and GenericFilter to BufferedADC.

Mix-in Classes
Unlike interface classes, which provide a more-or-less empty framework to be filled in by descendant
classes, mix-in classes do actually implement functionality. This functionality is contained in non-virtual
functions which may be declared "protected" to make them accessible only to classes that inherit from the
mix-in class. An example of such a mix-in class in BCI2000, is the Environment class, which provides
access to the Parameters and States that exist in the system. As GenericFilter inherits from Environment,
most BCI2000 components inherit from Environment without explicitly asking for it. However, any class that
wants to access parameters or states may inherit from the Environmentclass. Another example of a mix-in
class is the ApplicationWindowClient class. To its inheritants, it provides access to application windows,
taking care of window creation and deletion, and allowing filters in the application module to share windows
for drawing.

Client Classes
Client classes are at the bottom of the inheritance hierarchy. They implement inherited interfaces, and use
the functionality provided by mix-in classes, to actually get something done. E.g., the LinearClassifier class
implements the GenericFilter interface, using the mix-in functionality of the Environment class, resulting in a
BCI2000 component that applies a linear classifier to a stream of data.
The StimulusPresentationTask class implements the StimulusTask interface, which in turn is a
specialization of the GenericFilter interface, and inherits functionality from the ApplicationBase base class.

Class Hierarchies

GenericFilter
GenericFilter is an interface class that serves as a base class for all BCI2000 filter components, i.e.
components that process signals in the filter chain. GenericFilter also inherits from the Environment class,
and thus provides the ability to access parameters and states to its descendants. Specializations to
GenericFilter comprise GenericADC, and BufferedADC.

EnvironmentExtension
EnvironmentExtension is an interface class for BCI2000 components that do not process signals, and are
not part of the filter chain, but need to be notified of BCI2000 events such as Preflight, Initialize, or
StartRun. Typical descendants of EnvironmentExtension are utility classes such as LogFile or
RandomGenerator.

ApplicationBase
This class inherits from GenericFilter, and serves as a base class for application task filters in application
modules. To these, it provides a logging facility in form of the AppLog member object, and a random
number generator in its RandomNumberGenerator member object. Two specializations of ApplicationBase
exist that provide specialized interfaces to certain types of task filters: FeedbackTask, and StimulusTask.
These two interface classes define events that are specific to task filters proving feedback, and doing
stimulus presentation, respectively. E.g., the FeedbackTask defines an event FeedbackBegin which should
be handled by displaying, say, a feedback cursor on a feedback screen, while the StimulusTask defines an
event StimulusBegin, which is triggered when a stimulus is being displayed.

GraphObject and GraphDisplay


The GraphObject class defines an interface for classes that represent objects displayed on a
GraphDisplay. GraphObjects are attached to GraphDisplays, and have a rectangle that defines their
position and extent. GraphObject descendants implement handlers of the Paint event. The VisualStimulus
class inherits from both GraphObject and Stimulus base classes, providing a stimulus that draws itself on a
GraphDisplay when presented.

Stimulus
In stimulus-based application modules, an interface is needed for classes that represent stimuli. The
interface defines that stimuli may be presented, or hidden. A Stimulus descendant implements this
interface by providing appropriate code to present itself (showing an image on a display, or playing a
sound).

Target, SpellerTarget and Speller


For application modules that implement spellers, such as the P3Speller, there is a general Target interface
class for objects that can be selected by brain signals, and a specialized interface classes called
SpellerTarget. A SpellerTarget is linked to a on object that implements the Speller interface, and its action
is to enter text into the Speller from its Select event handler.

GenericSignal Class
Location
src/shared/types

Synopsis
Many classes in both Data Acquisition and Signal Processing work on signals, i.e., continuously flowing
data organized into channels and samples. The GenericSignal class contains floating point data
organized as a matrix of channels and "elements" (a generalized notion of samples -- e.g., spectrally
analyzed data might contain the spectrum of each channel as a list of "elements").

Methods
GenericSignal(int Channels, int Elements, SignalType=int16)
Initializes a GenericSignal object to the specified number of channels and elements. The signal type
argument may be one of

 SignalType::int16,
 SignalType::int32,
 SignalType::float32,
 SignalType::float24.
GenericSignal(SignalProperties)
Initializes a GenericSignal object to the properties defined by the SignalProperties object.

WriteToStream(ostream)/ReadFromStream(istream)
Read/write from/to std::iostream streams in human-readable ASCII format.

WriteBinary(ostream)/ReadBinary(istream)
Read/write from/to std::iostream streams as a BCI2000 signal message.

WriteValueBinary(ostream, channel, element)/ReadValueBinary(istream, channel,


element)
Binary I/O of individual values, depending on the signal's type. These functions are provided for use
in FileWriter components, and components that read data from input files.

Properties
SignalProperties Properties (rw)
The signal's properties, contained in a SignalProperties object.

int Channels (r)


The number of channels.

int Elements (r)


The number of elements.

SignalType Type (r)


The signal type.

number Value(Channel, Element) (rw)


The signal's value at the specified channel/element combination. Access to signal values is also possible
(and recommended) using parentheses notation on the signal object itself:
GenericSignal mySignal( 5, 4 );
mySignal( 2, 3 ) = 4.5;

SignalProperties Class
Location
src/shared/types

Synopsis
Sometimes, the number of channels, elements, and bytes required for storing values, are referred to as
"Signal Properties". There is a separate class, SignalProperties, for expressing those values, and
determining whether a given signal "fits" into another one, i.e., whether the values contained in one signal
may be copied into another signal without loss of information. GenericSignal class uses
a SignalProperties member to maintain information about its properties.

Methods
SignalProperties(Channels, Elements, SignalType=int16)
Initializes a SignalProperties object to the specified number of channels and elements. The signal
type argument may be one of

 SignalType::int16,
 SignalType::int32,
 SignalType::float32,
 SignalType::float24.
bool Accommodates(SignalProperties)
True if a signal of the argument's properties can be represented by a signal with the object's properties
without data loss, i.e. the number of channels and elements must at least match the argument's, and the
numeric range defined by the signal's type must at least equal the argument's.

float ChannelIndex(string Address), ElementIndex(string Address)


Returns a zero-based channel or element index corresponding to the specified address. The address may
be

 a label contained in the ChannelLabels or ElementLabels property,


 a value in physical units, with a unit matching the one from the ChannelUnit or ElementUnit property,
or
 a plain number, which is treated as a one-based index.

These interpretations are tried in the above order; if none matches, a negative index is returned, indicating
failure to interpret the address.

This function is provided to simplify conversion of parameter values into indices, allowing users to use
labels, physical units, or one-based indices as appropriate for the signal that is to be addressed.

Properties
int Channels, Elements (rw)
The number of channels and elements.
SignalType Type (rw)
The signal's numeric type.

string Name (rw)


The signal's name. When a signal is sent to the operator module via a GenericVisualization object, its
name is used as a visualization window title.

LabelIndex ChannelLabels, ElementLabels (rw)


Labels of channels and elements, represented as LabelIndex objects.

float SamplingRate (r)


The sampling rate of the signal, given in Hz, or 0 when the dimension in element direction is not time.
SamplingRate is a readonly property; to adapt a signal's sampling rate, set its ElementUnit's Gain property
to the inverse of the desired sampling rate.

float UpdateRate (rw)


The update rate of the signal, given in Hz. This number shows how often a signal is updated with new
values, irrespective of its dimensions. Note that in general SamplingRate is not equal to
UpdateRate*Elements.

PhysicalUnit ChannelUnit, ElementUnit, ValueUnit (rw)


Physical units associated with respective dimensions. Typically, there is no physical unit in the channel
dimension; in the element dimension, the physical unit would be temporal, measured in seconds, or a
frequency, measured in Hz; in the value dimension, the physical unit would be a voltage, or a power
thereof. Physical units are represented as objects of type PhysicalUnit.

bool IsEmpty
True if at least one of channels and elements is zero.

Handling Errors
Types of Errors
We assume that all errors we need to consider fall into one of the following categories, each of which
implies a different type of approach to error avoidance/error handling:

 Parameter Setup Errors


 Runtime Errors
 Logic (Programming) Errors

Parameter Setup Errors


Definition of the Term
This category covers anything a user can do wrong by using a program with parameters that are out of
range, inconsistent, or otherwise erroneous (e.g., by specifying an output file at a location where the user
has no write permission).

Parameter setup errors, when unhandled, become runtime errors.


Strategies
As a guideline for approaching Parameter Setup Errors we adopt the following principle: "Whatever a user
does from within an application program should never make that application crash."

In BCI2000, this translates into a thorough parameter check done by each module before any parameter
settings are actually applied to the system.

Parameter checking should comprise

 Range and Consistency checks, whereby ranges generally depend on the values of other parameters;
 Signal property checks: Does the output signal of one filter meet the next filter's requirements for its
input signal?
 Resource availability checks:

 Are needed system resources available? (E.g., is it possible to open a required sound output
device?)

 Are auxiliary files (e.g., media files) available and readable?

 Do output files have legal file names? Are output files writeable? (We could even check whether
there is enough space left to write the EEG file, but this is not practical because a concurrent
process might use up the space while our system runs.)

In each of these cases, the user should get appropriate feedback guiding her towards fixing the problem.

Whenever the system tries to fix a parameter setup error by using some default set of parameters, it should
do so only if

 it presents the user with a warning that tells her what it did and why it did so, and if
 the automatically fixed parameters are treated as if changed by the user, i.e. with a parameter check
performed on them.

Otherwise, people may unknowingly using a system that doesn't do what they want it to, or have a system
that creates new parameter inconsistencies when trying to fix others.

User Interface Details


The user interface for Parameter Setup Error handling is, along with the parameter setup dialog, part of the
operator module. A first implementation of a GUI based user interface consists in a floating, non-modal
error window popping up from the operator module that presents a list of error related textual messages to
the user, allowing for browsing error messages while changing respective parameters via the parameter
setup dialog. After the next parameter check, the operator module will close that window or replace its
contents based on the result of the check. Parameter checking occurs when the user clicks the "SetConfig"
button in the operator main window, followed by actually applying parameters if the check was successful.

Runtime Errors
Definition of the Term
This category covers everything that can go wrong in the course of running an application program, insofar
as that malfunction is due to a lack of resources in the underlying system required for proper operation (i.e.,
not due to a programming error). Assuming that parameter checking has been implemented properly as
outlined above, we can narrow the term `Runtime Error' to cases for which the following statement holds: A
runtime error occurs whenever the system runs out of resources that were still available during parameter
checking.

Typical reasons for this kind of error are

 the system runs out of disk space while recording data,


 files being moved, trashed, or locked by a concurrent process,
 a network connection becomes unavailable.

Runtime errors, when unhandled, become logic errors because the code implies assumptions that no
longer hold once a runtime error has occurred.

Strategies
In a properly designed and implemented system, runtime errors, in the restricted sense described above,
will not occur frequently. However, as they are caused by undesired circumstances outside the scope of
the application program itself, it seems important to provide information to the user that is as detailed as
possible in order to enable her to prevent this type of situation in the future, and to make her aware of the
fact that the application program depends on her willingness to provide a smooth operating environment.
After displaying a message of this kind, it seems appropriate to simply abort execution altogether, while
trying to avoid a loss of the data acquired up to that time.

User Interface Details


In general, it is desirable to have runtime errors displayed along with the user interface of the operator
module. However, as this requires a working connection between the module where the error occurs, and
the operator module, this may not always be possible. Therefore, in addition to an operator-based error
reporting interface, each module should have a less demanding mechanism to provide error information to
the user, e.g., a local log file.

Logic Errors
Definition of the Term
Logic, or programming, errors in general can be due to a programmer who, in his or her code, implicitly or
explicitly makes assumptions that do not always hold.

Strategies
Programming errors are not supposed to occur at all in a tested version of an application. Therefore,
instead of trying to 'handle' them, it is important to make them show up as close to their point of origin in the
code as possible, by frequently and explicitly checking whether implicit assumptions actually hold, and
aborting execution with an error message if this is not the case.

Typically, such checks use an "assertion" facility provided by the programming language. BCI2000
provides its own bciassert macro in BCIAssert.h to make sure that failed assertions result in
messages that are displayed by the BCI2000 operator module. Unlike the standard assert macro, BCI2000
assertions do not evaluate to empty in release builds.

Aside from that, writing code as explicit, general, and simple as possible greatly reduces the possibility of
making logic errors in the first place.

User Interface Details


As programming errors are errors about which a user can do nothing, simply aborting the program or
module with an error message seems appropriate.
Implementation Details
Interface to the Programmer
Reporting Errors
For a simple and general way to provide user communication and a means of error reporting to a module's
programmer, there exist two global objects derived from std::ostream, one named bciout and the
other named bcierr, analogous to std::cout and std::cerr, where bciout is used to transfer
general messages and warnings while bcierrtakes actual errors. A code example then looks like this:

#include "BCIError.h"
...
using namespace std;
...
ofstream outputStream( fileName );
if( !outputStream.is_open() )
{
bcierr << "Cannot open the file \""
<< fileName
<< "\" for output"
<< endl;
}

Furthermore, for handling runtime errors from which it is difficult to recover, code may throw an exception
that will abort execution and eventually lead to an error message being sent to the operator module (for
framework related details see the section entitled "Implementation on the Framework Side"):

#include "BCIException.h"
...
if( ernie.find( bert ) != ernie.end() )
throw bciexception( "Ernie just ate Bert. I don't know how to tell the
story." );
tellMyStory( ernie, bert );
...

Checking Parameters
Checking parameters is done in a separate member function of the filter base class which, similar to the
member function that does the actual processing, takes input and output signal representatives as
parameters, thus allowing for signal property checking.

For the actual implementation, its declaration is as follows:

void GenericFilter::Preflight ( const SignalProperties& Input,


SignalProperties& Output ) const;
For a filter class derived from GenericFilter, this function is supposed to perform parameter checking
as described in the "Strategies" section. Instead of returning an error value, it writes possible error
messages into bcierr. Furthermore, it communicates dimensions of its output signal which it guarantees
not to exceed, and it does so by adjusting the properties of the second SignalProperties object in its
argument list, e.g.

Output = SignalProperties( Input.Channels(), 1 );

or

Output = SignalProperties( 0, 0 );

if it declares not to use its output signal.

The const declaration for its this pointer prohibits initialization functionality
from GenericFilter::Initialize() entering into Preflight(). Such behavior is unwanted
because it would corrupt the idea of performing a complete parameter check before actually altering the
state of a filter object.

A necessary condition for a correct implementation of the Preflight() function is that any parameter,
as well as any state that will be accessed during the processing phase, be accessed
from Preflight() at least once. For parameters and states defined by the filter itself (i.e. inside its
constructor), range and accessibility checks are automatically performed by the framework; parameters
and states defined by other filters must be explicitly accessed from Preflight(). If
a GenericFilter descendant fails to access an externally defined parameter or state
during Preflight(), the first access during the processing phase will result in a runtime error.

Accessing Environment Objects


We consider parameters and states part of the BCI2000 "environment". GenericFilter descendants
have access to that environment, analogous to the concept of environment variables found in some
operating systems. Internally, access to the environment is mediated through a mix-in-class
named Environment that provides accessor symbols to a filter programmer.

Low Level Access to Environment Objects is provided by the following symbols:

 Parameters syntactically behaves like a ParamList*,


 States behaves like a StateList*,
 and Statevector behaves like a StateVector*.

As an example,

float myParameterValue = 0.0;


Param* param = Parameters->GetParamPtr( "MyParameter" );
if( param )
myParameterValue = atof( param->GetValue() );
else
bcierr << "Could not access \"MyParameter\"" << endl;
Unlike true pointers, these symbols cannot be assigned any values, cannot be assigned to variables, or
have other manipulating operators applied. For example, the lines

delete Parameters;
Parameters = new ParamList;

will all result in compiler errors.

Convenient Access to Environment Objects is possible through a number of symbols which offer built-in
checking and error reporting:

 Parameter(Name)[(Index 1[, Index 2])


This symbol stands for the value of the named parameter. Indices may be given in numerical or textual
form; if omitted, they default to 0. The type of the symbol Parameter()may be numerical or a string type,
depending on its use. (If the compiler complains about ambiguities, use explicit typecasts.) If a parameter
with the given name does not exist, an error message is written into bcierr. If the specified indices do not
exist, no error is reported. In both cases, on read access, the string constant "0" resp. the number 0 is
returned.

Examples:

int myValue = Parameter( "MyParam" );


string myOtherValue = Parameter( "MyOtherParam" );
Parameter( "My3rdParam" )( 2, 3 ) = my3rdValue;

 OptionalParameter(Name[, Default Value])(Index 1[, Index 2])


This symbol behaves like the symbol Parameter() but will not report an error if the parameter does not
exist. Instead, it will return the default value given in its first argument. Assignments to this symbol are not
possible.

 State(Name)
This symbol allows for reading a state's value from the state vector and setting a state's value in the state
vector. Trying to access a state that is not accessible will result in an error reported via bcierr.

Examples:

short currentStateOfAffairs = State( "OfAffairs" );


State( "OfAffairs" ) = nextStateOfAffairs;

 OptionalState(Name[, Default Value])


Analagous to OptionalParameter(), this symbol does not report an error if the specified state does
not exist but returns the given default value. Assignments to this symbol are not possible.

 PreflightCondition(Condition)
This symbol is meant to be used inside implementations of GenericFilter::Preflight(). If the
boolean condition given as its argument is false, it will output an error message into bcierr containing the
condition given in its argument.

Example:

PreflightCondition( Parameter( "TransmitCh" ) <= Parameter( "SourceCh" ) );

If TransmitCh is greater than SourceCh, a message will be sent to bcierr and displayed to the user,
stating:

A necessary condition is violated. Please make sure that the following is


true: Parameter( "TransmitCh" ) <= Parameter( "SourceCh" )

Implementation on the Framework Side


The behaviour of the operator module in response to an error message that arrives from one of the
modules depends on its context, i.e., on the execution phase the system is in. That way, no additional
programming interface elements visible to a filter/module programmer are needed to implement an error
handling scheme as described in the "Handling Errors"section.

During the preflight phase, errors are Configuration Errors. A module's framework code
behind bcierr just collects error messages; on return from the preflight function, it sends those messages
to the operator module which then, from the contents of the message (i.e., whether it was empty or not),
determines whether the preflight was successful; on not receiving any message after some timeout it
assumes a broken connection or a crashed module.

(For now, a simple timeout scheme with a fixed timeout interval of 5s seems appropriate. In the future, one
might consider a module requesting additional timeout periods if it expects lengthy calculations.)

During all other phases, the code behind bcierr immediately (i.e., on flushing the std::ostream)
sends its message buffer to a log file as well as to the operator module, indicating a Runtime Error to the
operator module which will, in turn, halt the system,shut down the other modules, and display the message
to the user.

In addition, the top level exception handling code of each module contains similar functionality, sending an
exception's associated description string into a log file and to the operator module, if possible, then quitting
the module in which the exception occurred. This not only ensures a proper general handling of exceptions
within the framework but also allows a programmer to handle Runtime Errors by raising her own
exceptions, eliminating the need to take care of the error condition in the code following the detection of an
error.

Introduction
As BCI2000 modules are being used in various countries, there emerges some need to adapt user
interface elements to regional customs. Most notably, text that gives information to the subject requires
translation into the subject's native language, as often a knowledge of English cannot be assumed. Even
with a knowledge of English, reading text in a foreign language is a distracting factor that makes the
already demanding task of operating a BCI even more difficult.

This document explains the approach taken to manage localized (translated) user interface strings in
BCI2000 application modules, trying to meet the following goals:

 Flexibility: For the end user (operator), it should be possible to add translations into a additional
languages, and to modify translations he does not feel to be appropriate. This should be achievable
without making changes to the source code and recompiling the application module.
 Separability: For the user, switching languages should be done at a central place, i.e., by changing a
single parameter. For the programmer, adding localization capabilities to existing modules should be
possible with minimal changes to existing code. When writing new application modules, there should
be no need to consider localization issues in advance.
 Documentation: Documenting and providing a collection of existing translations into various
languages should be possible inside a module's source code.

Strategies
For a number of text values that have always been set from parameters (such as the Speller
module's TextToSpell parameter that holds the text the user is told to spell in copy spelling mode),
localization is not an issue because the user may just change the value as seems appropriate.

The remaining text items, according to the way they are specified, fall into two categories:

1. C string literals in *.cpp source files, and


2. text fields in GUI resource files (such as Borland *.dfm files).

For both categories, translations are kept in a single matrix type parameter that uses string labels, i.e., row
and column titles. Each column title represents the native English version of a text; row titles represent
languages for which translations exist. The user chooses amongst languages by specifying the desired
language in a second parameter; the BCI2000 framework will then use this table to look up a text's
translation, matching the text itself against column labels, and the target language against row labels. If no
match is found, it will leave the text unchanged.

For string literals in *.cpp files, the strategy is to simply put a function layer around the string, i.e., to send
it through a function that checks for a translation and exchanges the text if there is a match. Introducing
localizability into existing code implies only a very small amount of change compared to, e.g., introducing a
separate parameter for each string. In the former case, one needs only wrap the string literal into a function
call in-place; in the latter case, one would have to add a parameter line to a possibly remote filter class
constructor, read that parameter from inside that filter's Initialize() call, and put it into the object that
actually holds the string which might require introduction of additional accessor functions.

For strings specified in GUI resource files, the strategy is to supply a function that, very generally,
examines string properties of GUI objects, replacing them with their localized versions if applicable. This
function needs to be called explicitly from inside the Initialize() function of
the GenericFilter descendant that determines the GUI object's behavior (in most cases, this is the
application module's TTask class).

Implementation
The implementation is centered around a Localization class declared
in Application/shared/Localization.h.

Interface to the operator user


The Localization class adds two parameters to the system that govern its localization behavior:

 Language defines the language into which strings are translated; if its value matches one of
the LocalizedStrings row labels, translations will be taken from that row; otherwise, strings will
not be translated. A value of Default results in all strings keeping their original values.
 LocalizedStrings defines string translations. Strings that do not appear as a column label will not
be translated. Also, strings with an empty translation entry inLocalizedStrings will not be
translated.
Interface to the programmer
The LocalizedStrings parameter is empty by default. Although a user may add translations in desired
languages to the empty matrix by manually using the matrix editor of the operator module, translations will
preferably be provided in a filter constructor by listing them as in the following example:

#include "Localization.h"
...
TTask::TTask()
{
...
LANGUAGES "Italian",
"French",
BEGIN_LOCALIZED_STRINGS
"Yes",
"Si",
"Oui",
"No",
"No",
"Non",
END_LOCALIZED_STRINGS
...
}

If the LocalizedStrings matrix was empty before the TTask constructor gets called, it will have these
entries after execution of the constructor:

Yes No
Italian Si No
French Oui Non

There may be any number of translation tables inside filter constructors, with their entries being added to
the existing ones, or overriding entries that already exist.

Once entered, the translations contained in LocalizedStrings are applied via two mechanisms:

 The function LocalizableString() takes a string as an argument and returns the appropriate
entry from LocalizedStrings, or the unmodified string if no entry can be found. For example,
instead of
TellUser( "Well done!" );
one would write
#include "Localization.h"
...
TellUser( LocalizableString( "Well done!" ) );
to have a translation for "Well done!" looked up in LocalizedStrings.

 The function ApplyLocalizations() takes a pointer to a GUI object (for the Borland VCL, this
would be a TForm*) and translates all localizable text contained within it. This function must be called
during GenericFilter:: Initialize for each GUI object generated from a resource file.
Further implementation details

 You should not use LocalizableString() on string constants used before the first call
to GenericFilter::Initialize() or for initializing static or global objects of any kind because
localization information used will not be available at global initialization time, and local static variables,
once initialized, will not be updated appropriately.

 Language names are case-insensitive. You may use any string for a language name but as a
convention we suggest its most common English name, as in Italian, Dutch, French,
German, with international country abbreviations as optional regional qualifiers as in EnglishUS,
EnglishGB, GermanA, GermanCH if necessary.

table

 Encoding of non-ASCII characters follows the UTF8 convention. To ensure platform independent
readability of source code files, there are macros that define HTML character names to their UTF8
encoded strings. This allows you to write
"Sm" oslash "rrebr" oslash "d"
for "Smørrebrød" (cf. table 1).
Functions specific to the Library
int BCI2000Release( void* )
purpose: Releases a string buffer, or other object allocated by the library. arguments: String pointer, or
handle to object allocated by one of the library functions. returns: 1 if successful, 0 otherwise.

void* BCI2000Remote_New( void )


purpose: Creates a new BCI2000Remote object. arguments: None. returns: A handle to the newly created
object.
int BCI2000Remote_Delete( void* )
purpose: Deletes a BCI2000Remote object. arguments: Handle to an existing BCI2000Remote object.
returns: 1 if successful, 0 otherwise.

BCI2000Remote Member Functions


The following functions are property accessors, and member functions of BCI2000Remote objects. For
documentation, see "Programming Reference:BCI2000Remote Class" on the BCI2000 wiki. In the C-style
interface, each function takes a handle to an existing BCI2000Remote object as its first argument. Use
BCI2000Remote_New() to create such objects. When a function returns a C string, the caller is responsible
for releasing that string once it is done with it. This is done by calling BCI2000Release() on the string.
Function that return a C string may return NULL to indicate failure.

double BCI2000Remote_GetTimeout( void* )


void BCI2000Remote_SetTimeout( void*, double timeout )
const char* BCI2000Remote_GetOperatorPath( void* )
void BCI2000Remote_SetOperatorPath( void*, const char* )
int BCI2000Remote_GetWindowVisible( void* )
void BCI2000Remote_SetWindowVisible( void*, int visible )

enum
{
BCI2000Remote_Invisible = 0,
BCI2000Remote_Visible = 1,
BCI2000Remote_NoChange = 2,
};

const char* BCI2000Remote_GetWindowTitle( void* )


void BCI2000Remote_SetWindowTitle( void*, const char* )
const char* BCI2000Remote_GetTelnetAddress( void* )
void BCI2000Remote_SetTelnetAddress( void*, const char* )
int BCI2000Remote_GetConnected( void* )
const char* BCI2000Remote_GetResult( void* )
const char* BCI2000Remote_GetSubjectID( void* )
void BCI2000Remote_SetSubjectID( void*, const char* )
const char* BCI2000Remote_GetSessionID( void* )
void BCI2000Remote_SetSessionID( void*, const char* )
const char* BCI2000Remote_GetDataDirectory( void* )
void BCI2000Remote_SetDataDirectory( void*, const char* )
int BCI2000Remote_Connect( void* )
int BCI2000Remote_Disconnect( void* )
int BCI2000Remote_Execute( void*, const char* )
NB: This function returns the last command's execution status, which is a true integer, with a value of 0
indicating success in most cases. All other int-returning functions return a boolean integer which is 1 on
success, and 0 on failure.

int BCI2000Remote_StartupModules( void*, const char* )


Provide a zero-delimited list of C strings, containing module names with command line arguments in the
form

const char* list = "module1 -arg1 -arg2\0module2 -arg1\0module3 -arg1\0\0";

int BCI2000Remote_LoadParametersLocal( void*, const char* )


int BCI2000Remote_LoadParametersRemote( void*, const char* )
int BCI2000Remote_SetConfig( void* )
int BCI2000Remote_Start( void* )
int BCI2000Remote_Stop( void* )
int BCI2000Remote_Quit( void* )
const char* BCI2000Remote_GetSystemState( void* )
int BCI2000Remote_GetControlSignal( void*, int, int, double* )
int BCI2000Remote_SetParameter( void*, const char* name, const char*
value )
const char* BCI2000Remote_GetParameter( void*, const char* name )
int BCI2000Remote_AddStateVariable( void*, const char* name, unsigned
int bitWidth, double initialValue )
int BCI2000Remote_SetStateVariable( void*, const char*, double )
int BCI2000Remote_GetStateVariable( void*, const char*, double* )
int BCI2000Remote_SetScript( void*, const char*, const char* )
const char* BCI2000Remote_GetScript( const char* )
Programming Reference:GenericADC Class
The GenericADC class is a base class for data acquisition components. Its main purpose is to provide a
common ancestor for these components, to make them identifiable at run-time using run-time-type-
information (RTTI). Thus, it forwards the GenericFilter class interface, except for the following changes:

Purely Virtual Halt


Unlike GenericFilter, the Halt member function is declared "purely virtual" in GenericADC. This
implies that a derived class must provide its own implementation of Halt, even if that implementation may
be empty.

This is to stress the importance of a proper implementation of Halt for smooth system operation. More
often than not, a source module will rely on asynchronous activity in a hardware driver, or a separate data
acquisition thread maintained by the GenericADC descendant itself. Such asynchronous activity must be
halted from the Halt function in order to ensure proper behavior of the source module at reconfiguration
and shutdown time.

Empty Output
For GenericADC descendants, the output signal's dimensions should be specified as "empty" in
the Preflight function.

Programming Reference:GenericFileWriter Class


The GenericFileWriter class is a specialization of the GenericFilter class. It is supposed to
provide an interface to classes that implement data output into various file formats. Typically, such classes
do not inherit from GenericFileWriter directly; rather, they inherit from the FileWriterBase class
that additionally implements functionality common to all FileWriters, such as dealing with file naming,
location, opening and closing. FileWriterBase maintains an OutputStream property that provides
access to astd::ostream object bound to the current output file.

The GenericFileWriter interface is identical to the GenericFilter interface, except for the
following:

Publish() member function


Unlike other descendants of GenericFilter, GenericFileWriter descendants are supposed to
issue parameter and state requests from a separate Publish() member function rather than their
constructors. Within the Publish() member, parameter and state requests may be specified using
the ..._DEFINITIONS macros as in constructors. At the beginning of the Publish() member function,
the inherited implementation of Publish() should be called, as in the following example:

void
BCI2000FileWriter::Publish() const
{
FileWriterBase::Publish();

BEGIN_PARAMETER_DEFINITIONS
"Storage string StorageTime= % % % % "
"// time of beginning of data storage",
END_PARAMETER_DEFINITIONS
}

Write() rather than Process()


Actual data output is supposed to take place from the Write() member function, which receives
a GenericSignal and a StateVector argument. There, the state vector argument represents the
state vector as it existed at the time when the signal, as provided in the signal argument, was acquired.
For GenericFileWriter descendants, no Process() member function will be called.

Programming Reference:BCI2000FileReader Class


The BCI2000FileReader class provides an interface to data stored in a single BCI2000 data file.

Contents
[hide]

 1 Methods
o 1.1 BCI2000FileReader([file name])
o 1.2 Open(file name, [buffer size=50kB])
o 1.3 RawValue(channel, sample)
o 1.4 CalibratedValue(channel, sample)
o 1.5 ReadStateVector(sample)
 2 Properties
o 2.1 IsOpen (r)
o 2.2 ErrorState (r)
o 2.3 NumSamples (r)
o 2.4 SamplingRate (r)
o 2.5 SignalProperties (r)
o 2.6 FileFormatVersion (r)
o 2.7 HeaderLength (r)
o 2.8 StateVectorLength (r)
o 2.9 Parameters (r)
o 2.10 Parameter (r)
o 2.11 States (r)
o 2.12 State (r)
o 2.13 StateVector (r)
 3 Performance Considerations
 4 See also

Methods
BCI2000FileReader([file name])
With a file name given as an argument, the constructor will try to open the specified file.

Open(file name, [buffer size=50kB])


Will open the specified data file. Use the IsOpen and ErrorState properties to check whether a file is
actually open. In a second argument, the size of BCI2000FileReader's internal data buffer may be
specified in bytes; when no buffer size argument is given, a default size of 50kB is used.

RawValue(channel, sample)
Sets the current sample position to the value specified in the second argument, and returns the raw sample
value for the channel specified in the first argument.

CalibratedValue(channel, sample)
Sets the current sample position to the value specified in the second argument, and returns the calibrated
sample value for the channel specified in the first argument. The calibrated sample value is obtained by
applying channel-specific sample offsets and gains as present in the
file's SourceChOffset and SourceChGain parameters.

ReadStateVector(sample)
Sets the current sample position to the specified value, and reads state vector data.

Properties
IsOpen (r)
true when a file has been opened successfully, false otherwise.

ErrorState (r)
One of

 NoError,

 FileOpenError,

 MalformedHeader.
NumSamples (r)
The number of samples in the currently opened data file.

SamplingRate (r)
The currently opened data file's sampling rate.

SignalProperties (r)
A SignalProperties object describing the current file's data format, number of channels, sample block
size (in its Elements property), and physical units.

FileFormatVersion (r)
A string describing the file's format as specified by the file header. Currently, this is "1.0" or "1.1".

HeaderLength (r)
The data file's header length in bytes.

StateVectorLength (r)
The state vector's length in bytes.

Parameters (r)
A ParamList object that contains all parameters present in the file.

Parameter (r)
Access to an individual parameter, following the syntax provided by the Environment class.

States (r)
A StateList object containing all state variables present in the file.

State (r)
Access to an individual state variable, following the syntax provided by the Environment class. The
returned value will match the state variable's value at the current sample position, as specified to a
previous call to one of the RawValue, CalibratedValue, or ReadStateVector methods.

StateVector (r)
Access to the full state vector, with state values matching those at the current sample position.

Performance Considerations
To improve performance when reading data, BCI2000FileReader uses an internal buffer which is 50kB
in size by default but may be set to a different value using an optional second argument
to BCI2000FileReader::Open().

Buffering strategy is optimized for sequential forward access, which is supposed to be the most relevant
use case. Buffering is non-overlapping, with the requested sample always the first one in the buffer in case
the buffer needs updating. In case of sequential access in reverse direction, this buffering scheme breaks
down and becomes maximally inefficient because the entire buffer is updated on each access.

In a BCI2000 data file, data is organized such that, for each sample, values from all channels and states
are stored, followed with all values from the next sample point, etc. When reading data from multiple
channels, this implies that it is favorable to iterate over channels in an inner loop, and over samples in
an outer loop; otherwise, buffering will become less efficient by a factor identical to the number of channels
read.

Programming Reference:IIRFilterBase Class


Contents
[hide]
 1 Location
 2 Synopsis
 3 FilterDesign Library
o 3.1 FilterDesign::Butterworth
 3.1.1 Order(order)
 3.1.2 Lowpass(corner frequency)
 3.1.3 Highpass(corner frequency)
 3.1.4 Bandpass(low corner, high corner)
 3.1.5 Bandstop(low corner, high corner)
 3.1.6 TransferFunction
o 3.2 FilterDesign::Chebychev
 3.2.1 Ripple_dB(amplitude)
o 3.3 FilterDesign::Resonator
 3.3.1 QFactor(q)
 3.3.2 Bandpass(center frequency)
 3.3.3 Bandstop(center frequency)
 3.3.4 Allpass(center frequency)
 4 Remarks
 5 Background Information
 6 Examples
o 6.1 Creating a BCI2000 Filter
o 6.2 Obtaining Filter Coefficients
 7 See also

Location
BCI2000/src/shared/modules/signalprocessing

Synopsis
The IIRFilterBase class is a base class for BCI2000 filters that provide digital filters. Classes derived
from IIRFilterBase are supposed to implementIIRFilterBase::DesignFilter(), specifying
filter behavior by means of a transfer function. Differently from typical IIR filter implementations, which
expect the transfer function in terms of filter coefficients, the BCI2000 IIRFilterBase class expects the
transfer function in terms of its numerator's roots ("zeros") and denominator's roots ("poles"); this approach
improves numerical stability at higher filter orders.

FilterDesign Library
While any method may be used to determine the transfer function, the FilterDesign library provides a
convenient way to compute a transfer function from desired filter properties. It is located
at BCI2000/src/extlib/math/FilterDesign, and was written for BCI2000 based on public-
domain code by A.J. Fisher; it provides a number of classic filter design methods in form of C++ classes.
To use such a class, instantiate it, use its member functions to configure parameters, and obtain
its TransferFunction property (see the example section below).

CAVEAT: When specifying frequencies, the FilterDesign library expects them normalized to the sampling
rate, such that the Nyquist frequency becomes 0.5. This is different from Matlab, where filter design
functions expect frequencies normalized to the Nyquist frequency, such that the Nyquist frequency
corresponds to 1.0.

FilterDesign::Butterworth
Order(order)
Set the filter order to the specified value.

Lowpass(corner frequency)
Design a low pass with the specified corner frequency, i.e. suppress signals above the given frequency.
Frequencies are specified in terms of the sampling rate, and expected to be below 0.5 (the Nyquist
frequency).

Highpass(corner frequency)
Design a high pass with the specified corner frequency, i.e. suppress signals below the given frequency.

Bandpass(low corner, high corner)


Design a bandpass with the specified corner frequencies, i.e. suppress signals outside the given frequency
range.

Bandstop(low corner, high corner)


Design a bandstop with specified corner frequencies, i.e. suppress signals inside the given frequency
range.

TransferFunction
Returns a transfer function as a rational polynomial with complex roots as declared
in BCI2000/src/extlib/math/Polynomials.h. From the Ratpoly object returned, numerator
and denominator polynomials may be extracted
using Ratpoly::Numerator() and Ratpoly::Denominator(), respectively. Both functions return
a polynomial, from which roots may be obtained using Polynomial::Roots(), and coefficients may be
obtained using Polynomial::Coefficients().

FilterDesign::Chebychev
The Chebychev class provides an interface identical to the Butterworth class, and an additional
function to specify ripple amplitude.

Ripple_dB(amplitude)
Set the amplitude of the filter's passband ripples in dB.

FilterDesign::Resonator
Rather than through corner frequencies, the Resonator class is parameterized specifying a resonant
frequency in conjunction with a quality factor (which is inversely proportional to the resonance peak's
width).

QFactor(q)
Set the resonator's quality factor to the specified value.

Bandpass(center frequency)
Design a bandpass around the specified center frequency.

Bandstop(center frequency)
Design a bandstop around the specified center frequency.

Allpass(center frequency)
Design a filter with a constant frequency response but altering phase at the center frequency.

Remarks

 You may obtain a serial combination of filters by multiplying their individual transfer functions prior to
extracting gain, zeros, and poles.

 The actual IIR filter implementation uses a sequence of order-one filter stages corresponding to pairs
of zeros and poles. Thus, there is no limitation to the filter order, as it would be the case for
implementations based on coefficients, due to numerical instability.

 Roots common to both numerator and denominator will be automatically removed from the overall
transfer function, so the combined filter may be of an order which is lower than the sum of individual
filter orders.
Background Information
The effect of a linear filter may be viewed in terms of a transfer function. When transforming both the filter
and its input signal from the time domain into a more convenient representation (applying a Z transform),
the input is transformed into a function over the complex plane, and the effect of the linear filter is

transformed into multiplication with another rational transfer function defined over the complex
plane, i.e. is a fraction consisting of a polynomial in as its numerator, and another fraction
consisting of a polynomial in as its denominator (for more information,
see http://en.wikipedia.org/wiki/Linear_filter#Mathematics_of_filter_design):
where the and are called coefficients, the are called zeros because H assumes a value of
zero whenever , and the are called poles because H goes to infinity wherever ,
and is a gain factor.

Importantly, the effect of applying filters in series is represented by multiplication of the two transfer
functions. In reverse, the effect of a single filter may be decomposed into a series of filters by writing H as a
product of transfer functions.

Filter design methods, such as Butterworth's or Chebychev's, define a filter by placing H's zeros and
poles in the complex plane, with the number of zeros and the number of poles corresponding to twice the
filter's order.

Often then, filters are implemented in terms of a series of delays for input and output samples, and
coefficients associated with those delays, such that the current output sample is computed as the
difference of past input samples, weighted with input coefficients , and past output samples, weighted
with output coefficients . Comparing such a filter's action to that of a filter with transfer function H, one
finds that input coefficients correspond to the polynomial coefficients of H's numerator, and output
coefficients ai correspond to the polynomial coefficients of H's denominator. A standard procedure in filter
design is thus to take a filter's poles and zeros, and convert them into coefficients and by
expanding numerator and denominator polynomial into coefficient form, and dividing both by to
obtain the correct order of coefficients.

While this approach is computationally most efficient, it suffers from a lack of computational accuracy, and
resulting instability. The cause of this instability is the fact that arbitrarily large terms need to cancel out in
the difference between weighted input and output samples, but may fail to do so due to roundoff errors.

In BCI2000, a different approach is taken. Taking raw zeros and poles, the transfer function is first
simplified by removing factors that cancel out. Then, it is taken apart into a product of transfer functions
with a single zero and a single pole each, corresponding to a series of filters, each of order 1. Thus, in
BCI2000 IIR filters may be of arbitrary order without risk of instability (provided the filter's poles and zeros
meet the criteria for stability in the general sense, of course).

Examples
Creating a BCI2000 Filter
To implement a 2nd order Butterworth low pass filter, you might derive a
class ButterworthLP from IIRFilterBase, with its declaration being

class ButterworthLP : public IIRFilterBase


{
public:
ButterworthLP();
virtual ~ButterworthLP() {}
private:
virtual void DesignFilter( const SignalProperties&,
IIRFilterBase::Real& gain,
IIRFilterBase::ComplexVector& zeros,
IIRFilterBase::ComplexVector& poles ) const;
};

The filter's corner frequency is specified in a parameter "ButterworthLPCorner":

ButterworthLP::ButterworthLP()
{
BEGIN_PARAMETER_DEFINITIONS
"Filtering float ButterworthLPCorner= 30Hz % % % // Low pass corner
frequency",
END_PARAMETER_DEFINITIONS
}

In the DesignFilter method, we use the FilterDesign library to obtain poles and zeros from parameters:

void
ButterworthLP::DesignFilter( const SignalProperties& inSignalProperties,
IIRFilterBase::Real& outGain,
IIRFilterBase::ComplexVector& outZeros,
IIRFilterBase::ComplexVector& outPoles )
const
{
float corner = MeasurementUnits::ReadAsFreq( Parameter(
"ButterworthLPCorner" ) )
/ inSignalProperties.Elements() * Parameter(
"SampleBlockSize" );
if( corner < 0.0 || corner > 0.5 )
bcierr << "ButterworthLPCorner exceeds range" << endl;

Ratpoly<FilterDesign::Complex> tf = FilterDesign::Butterworth()
.Order( 2 )
.Lowpass( corner )
.TransferFunction();
outGain = 1.0 / abs( tf.Evaluate( 1.0 ) ); // make sure that LF gain is
unity
outZeros = tf.Numerator().Roots();
outPoles = tf.Denominator().Roots();
}

For an example that combines a notch filter with a high pass filter, please refer to the SourceFilter's source
code.

Obtaining Filter Coefficients


In case you need to use a filter designed by the FilterDesign class with Matlab's filter() function, or any
other direct form II transposed filter implementation, use the FilterDesign::ComputeCoefficients() function.

Ratpoly<FilterDesign::Complex> tf = FilterDesign::Butterworth()
.Order( 2 )
.Lowpass( 10 / 500 )
.TransferFunction();
FilterDesign::ComplexVector a, b;
FilterDesign::ComputeCoefficients( tf, b, a );

NOTE: When comparing filter coefficients with Matlab's, consider that Matlab's convention for specifying
frequencies differs from that of FilterDesign. For more information, see theCaveat above.

Programming Reference:ApplicationBase Class


Contents

[hide]

 1 Location
 2 Synopsis
 3 Usage
 4 Access to Application Windows
 5 RandomNumberGenerator
 6 Application Log
 7 Remarks
 8 See also

Location
BCI2000/src/shared/modules/application
Synopsis
The ApplicationBase class bundles base functionality which is useful for any BCI2000
application module. This base functionality comprises

 generation of random numbers,


 maintaining an application log file, and application log messages to the operator user.
Usage
An application module uses the ApplicationBase class by deriving its task
filter from ApplicationBase rather than GenericFilter directly. It has then access to the
facilities explained below.

Access to Application Windows


The ApplicationBase class inherits from the ApplicationWindowClient class, and thus
provides access to framework-managed application windows. To access, and possibly
create, the main application window from your ApplicationBase-derived application filter
class, initialize an ApplicationWindow reference in its constructor with
theApplicationWindowClient::Window() function:

class MyTaskFilter : public ApplicationBase


{
...
private:
ApplicationWindow& mrWindow;
...
};

MyTaskFilter::MyTaskFilter()
: mrWindow( ApplicationWindowClient::Window() )
{
...
}

MyTaskFilter::Initialize( ... )
{
...
ImageStimulus* pImageStimulus = new ImageStimulus( mrWindow );
...
}

You may also test windows for existence from the Preflight() function, and only use them
when they already exist. This way, your code can adapt to the presence of certain filters in
the application module. For more details, see
the ApplicationWindowClient class reference page.

RandomNumberGenerator
The ApplicationBase class provides an object of type RandomGenerator which may be
used to obtain integer pseudo-random numbers uniformly distributed over a specified range:

int rnd = RandomNumberGenerator(10);

will assign a number between 0 and 9 to the variable named "rnd". Compared with other
options to obtain pseudo-random numbers, the advantage of
usingApplicationBase::RandomNumberGenerator object is that its behavior is governed
by a global random seed value; this allows consistent, predictable BCI2000 behavior, e.g. for
testing purposes. For more information, refer to the RandomGenerator reference page.

Application Log
Typically, a BCI2000 application module displays messages to the operator user to indicate
that a new trial has started, a target has been hit or missed, or to display statistics about the
current run. Often, this is information, in conjunction with additional, more detailed
information is written into a log file, and that log file is maintained side-by-side with recorded
data in the current session directory.
The ApplicationBase class provides an object named AppLog as a convenient interface to
write messages to screen, log file, or both at the same time. That AppLog object uses
a LogFile object to write into a log file; it uses a GenericVisualization object to write
messages into an operator window.
The AppLog object is a <std::ostream>, and may be used as in the following examples:

// Write a message to the log file only


AppLog.File << "This message goes into the log file." << endl;
// Write a message to the screen only
AppLog.Screen << "This message goes into an operator window." <<
endl;
// Write a message to both screen and file:
AppLog << "This message is displayed by the operator module, and
written into the log file." << endl;

Make sure to include the "endl" as nothing is written until the AppLog receives a newline
command.

The application log file is located in the current session directory, and named after the
current session; it carries an .applog extension.

Operator log window messages have APLG as their visualization source ID.
Remarks
Typically, a BCI2000 application module will implement a paradigm that belongs to one of
two categories:

 evoking responses using stimulus presentation,


 continuously presenting feedback of brain activity.
A BCI2000 task filter implementing one of these paradigms will not inherit from
the ApplicationBase directly but either from the StimulusTask or FeedbackTask. Still, such
a task filter has access to all functionality provided by ApplicationBase.

Programming Reference:ApplicationWindowClient
Class
ApplicationWindowClient is a mix-in class that provides its inheritants with access to application windows,
which are instances of the ApplicationWindow class.ApplicationWindow instances have names,
with the default name being "Application". A window's name may be used to obtain
a DisplayWindow reference to the window, e.g.

DisplayWindow& applicationWindow = Window( "Application" );


applicationWindow.SetTop( 15 );

Const access to the underlying list of windows is provided via


the ApplicationWindowClient::Windows pointer. This allows to write

if( Windows->Exists( "Application" ) )


{
DisplayWindow& applicationWindow = Window( "Application" );
TextStimulus* pStimulus = new TextStimulus( applicationWindow );
..
}

It also allows to iterate over existing windows:

for( int i = 0; i < Windows->Size(); ++i )


{
bciout << ( *Windows )[i].Name() << endl;
}

ApplicationWindow instances are created by using the ApplicationWindowClient::Window(


name ) function during a module's construction phase, typically from aGenericFilter descendant's
constructor (for an example, see the ApplicationWindow class page. If an application window with that
name already exists, it will be shared amongst filters which access it.

If you don't want to create an application window, but only access an existing one, you need to declare
access to that window during the preflight phase. In your Preflight()member function, write:

Window( "Application" );

When no such window exists, a preflight error will be reported. Unless the window has been accessed
during the construction phase, not declaring access to the window during the preflight phase will result in
an error message when calling ApplicationWindowClient::Window() from elsewhere.

Programming Reference:GraphDisplay Class


Contents
[hide]

 1 Location
 2 Synopsis
 3 Methods
o 3.1 GraphDisplay()
o 3.2 ~GraphDisplay()
o 3.3 DeleteObjects()
o 3.4 Invalidate()
o 3.5 InvalidateRect(GUI::Rect)
o 3.6 Update()
o 3.7 GUI::Rect NormalizedToPixelCoordinates(GUI::Rect)
o 3.8 GUI::Rect PixelToNormalizedCoordinates(GUI::Rect)
o 3.9 Paint(RegionHandle=NULL)
o 3.10 Change()
o 3.11 Click(int x, int y)
o 3.12 QueueOfGraphObjects ObjectsClicked()
o 3.13 ClearClicks()
o 3.14 BitmapImage BitmapData(width=0, height=0)
 4 Properties
o 4.1 GUI::DrawContext Context (rw)
o 4.2 RGBColor Color (rw)
 5 See also

Location
src/shared/gui

Synopsis
An object of type GraphDisplay refers to a graphical output rectangle, and a set of graphical objects that
are displayed within that rectangle.

GraphDisplay does not make assumptions on output device (screen, printer) or whether it refers to an
entire window, or a rectangle within a larger window.

The GraphDisplay class is contained within the GUI namespace. To use it, either qualify its
name: GUI::GraphDisplay (recommended for header files); or put a usingstatement on top of the file
(recommended for cpp files):

using namespace GUI;

Methods
GraphDisplay()
Initializes a GraphDisplay object such that it is not bound to any device or window.

~GraphDisplay()
The GraphDisplay destructor deletes all GraphObjects within the display.

DeleteObjects()
Removes and deletes all GraphObject instances contained in the GraphDisplay.

Invalidate()
Marks the entire GraphDisplay area for repainting.

InvalidateRect(GUI::Rect)
Marks a rectangle within the GraphDisplay for repainting. The rectangle is given in screen pixel
coordinates.

NOTE: The use of pixel coordinates has been introduced in rev 3599 to avoid artifacts from converting forth
and back between normalized and pixel coordinates. Originally, the rectangle was specified in normalized
coordinates.

Update()
Forces an immediate repaint of all invalidated areas in the GraphDisplay, making sure that changes
in GraphObject state are reflected in the device's frame buffer. In BCI2000 application modules, this
function should be called immediately before setting the StimulusTime time stamp.

GUI::Rect NormalizedToPixelCoordinates(GUI::Rect)
GUI::Rect PixelToNormalizedCoordinates(GUI::Rect)
These functions are used to convert between pixel/device coordinates, and normalized coordinates. Note
that, from application code, GraphObjects are positioned and sized using normalized coordinates
relative to the GraphDisplay's screen rectangle; in their OnPaint handlers, they are provided with pixel
coordinates.

Paint(RegionHandle=NULL)
Triggers a repaint of the GraphDisplay's contents. When a region handle is specified, it is used as a clip
during the paint operation, saving redraw of the entire GraphDisplayarea when only a subregion has
become invalid.

Change()
Triggers a Change event for all GraphObjects that are part of the display. For modifications in size or
draw context, this function is called automatically.

Click(int x, int y)
Triggers a Click event for all GraphObjects that are part of the display.
Unlike GraphObject::OnClick(), coordinates are pixel coordinates.

QueueOfGraphObjects ObjectsClicked()
When the user clicks inside a GraphDisplay, all objects' Click events are called to determine which of
them were below the mouse pointer when clicking. Objects that consider themselves clicked are added to a
queue, which is of type std::queue<GraphObject*>. This queue is accessible from
the ObjectsClicked function; from the queue, clicked objects may be retrieved using
its pop() member function.

ClearClicks()
Empties the queue of clicked objects.

BitmapImage BitmapData(width=0, height=0)


Returns the display's content, resampled to the specified target resolution, as a BitmapImage. When
both width and height are 0 (the default), the image is returned in its original size, and no resampling takes
place.

Properties
GUI::DrawContext Context (rw)
The draw context into which a GraphDisplay is supposed to render the GraphObjects it contains. A
draw context consists of a handle to an output device context (HDC in Windows), and a rectangle within
that device context's area.

RGBColor Color (rw)


The GraphDisplay's background color.

Programming Reference:GraphObject Class


Contents
[hide]

 1 Location
 2 Synopsis
 3 Methods
o 3.1 GraphObject(GraphDisplay, int zOrder)
o 3.2 Show()
o 3.3 Hide()
o 3.4 Invalidate()
o 3.5 Paint()
o 3.6 Change()
o 3.7 bool Click(GUI::Point)
 4 Properties
o 4.1 GraphDisplay Display (r)
o 4.2 bool Visible (r)
o 4.3 float ZOrder (rw)
o 4.4 enum AspectRatioMode (rw)
o 4.5 GUI::Rect DisplayRect (rw)
 5 Events
o 5.1 OnPaint(GUI::DrawContext)
o 5.2 OnChange(GUI::DrawContext)
o 5.3 bool OnClick(GUI::Point)
 6 Descendants
 7 See also

Location
src/shared/gui

Synopsis
The GraphObject class defines an interface for graphical objects displayed by a BCI2000
application. GraphObjects are tied to a GraphDisplay, which typically represents an application
window.
GraphObjects possess a z-order position that determines how they are drawn on top of each other. You
do not instantiate GraphObjects directly; rather, you create objects of classes that inherit
the GraphObject interface.

The GraphObject class is contained within the GUI namespace. To use it, either qualify its
name: GUI::GraphObject (recommended for header files); or put a usingstatement on top of the file
(recommended for cpp files):

using namespace GUI;

Methods
GraphObject(GraphDisplay, int zOrder)
The constructor of a graph object requires a reference to a GraphDisplay, and an integer that specifies
the object's z order, where lower values correspond to objects that are drawn on top of objects with larger z
order values.

Show()
Makes the object visible. Initially, objects are created in visible state.

Hide()
Makes the object invisible.

Invalidate()
Invalidates the object's bounding rectangle, i.e. marks it as needing to be repainted. Typically, this function
is called from a derived class, indicating that a change in object properties has occurred that requires a
repaint.

Paint()
Asks an object to paint itself by calling its OnPaint event handler.

Change()
Notifies an object of a change in display properties by calling its OnChange event handler.

bool Click(GUI::Point)
Tests whether the specified point is inside an object's bounding rectangle, and calls its OnClick event
handler if this is the case.

Properties
GraphDisplay Display (r)
The GraphDisplay object that was specified when the object was created.
bool Visible (r)
True if the object is visible, false if it is hidden. Use the Hide() and Show() methods to set this property.

float ZOrder (rw)


Determines the order in which GraphObjects are drawn. GraphObjects with smaller values of ZOrder are
drawn on top of those with larger values, hiding these.

enum AspectRatioMode (rw)


One of the following options:

AdjustNone

No adjustment is made.

AdjustWidth

The object's width is adapted to its contents, while keeping its height constant.

AdjustHeight

The object's height is adapted to its contents, keeping its width constant.

AdjustBoth

Both the object's height and width are adjusted to its content.

The exact behavior of aspect ratio adjustment depends on the object's type. E.g., for
bitmap images, AdjustBoth will size the image such that one image pixel corresponds to
one screen pixel; AdjustHeight and AdjustWidth will adjust such that the original aspect
ratio is preserved.

GUI::Rect DisplayRect (rw)


The bounding rectangle of the space that is occupied by the object. This is given in
coordinates relative to the size of the object's GraphDisplay; there, the upper left
corner corresponds to (0,0), the lower right corner to (1,1). When a GraphObject is
created, its display rectangle is empty.

Events
OnPaint(GUI::DrawContext)
From its OnPaint event handler, a GraphObject renders itself into a device context
and rectangle specified in its DrawContext argument. The DrawContext consists of an
OS device context handle, and a rectangle in device coordinates. Implementing this
function is mandatory for classes inheriting from GraphObject.

OnChange(GUI::DrawContext)
The OnChange event handler is called to notify the object that a change in size, position,
or output device has occurred. E.g., if the object maintains an internal bitmap buffer that
it uses to speed up paint operations, it should delete and re-create that buffer from
its OnChange handler to ensure consistency with the output draw context. Objects that
render themselves directly do not need to override the (empty) default handler.

bool OnClick(GUI::Point)
The OnClick event handler receives a point in normalized coordinates, and returns
whether it considers itself clicked. The default handler does nothing, and returns true.

Descendants
The GraphObject class is parent to the following class hierarchy:

GraphObject |-> Shape |-> RectangularShape


| |-> EllipticShape
|-> StatusBar
|-> ImageStimulus
|-> TextField
|-> Scene

Programming Tutorial:Implementing a Data


Acquisition Module
Data acquisition modules are factored into

 code required for any hardware, and

 code required to access a specific hardware.

You provide only specific code. This is in a function that waits for and reads A/D data (line 3 in the pseudo
code shown at Technical Reference:Core Modules), together with some helper functions that perform
initialization and cleanup tasks. Together these functions form a class derived from GenericADC.

Contents
[hide]

 1 Example Scenario
 2 Writing the ADC Header File
 3 ADC Implementation
 4 ADC Initialization
 5 Data Acquisition
 6 Adding the SourceFilter
 7 Finished

Example Scenario
Your Tachyon Corporation A/D card comes with a C-style software interface declared in a header
file "TachyonLib.h" that consists of three functions

#define TACHYON_NO_ERROR 0
int TachyonStart( int inSamplingRate, int inNumberOfChannels );
int TachyonStop( void );
int TachyonWaitForData( short** outBuffer, int inCount );

From the library help file, you learn that TachyonStart configures the card and starts acquisition to some
internal buffer; that TachyonStop stops acquisition to the buffer, and that TachyonWaitForData will
block execution until the specified amount of data has been acquired, and that it will return a pointer to a
buffer containing the data in its first argument. Each of the functions will return zero if everything went well,
otherwise some error value will be returned. Luckily, Tachyon Corporation gives you just what you need for
a BCI2000 source module, so implementing the ADC class is quite straightforward.

Writing the ADC Header File


In your class' header file, "TachyonADC.h", you write

#ifndef TACHYON_ADC_H
#define TACHYON_ADC_H

#include "GenericADC.h"

class TachyonADC : public GenericADC


{
public:
TachyonADC();
~TachyonADC();

void Publish();
void AutoConfig( const SignalProperties& );
void Preflight( const SignalProperties&, SignalProperties& ) const;
void Initialize( const SignalProperties&, const SignalProperties& );
void Process( const GenericSignal&, GenericSignal& );
void Halt();

private:
void* mHandle;
int mSourceCh,
mSampleBlockSize,
mSamplingRate;
};
#endif // TACHYON_ADC_H

ADC Implementation
In the .cpp file, you will need some #includes, and a filter registration:

#include "TachyonADC.h"
#include "Tachyon/TachyonLib.h"
#include "BCIError.h"

using namespace std;

RegisterFilter( TachyonADC, 1 );

From the constructor, you request parameters and states that your ADC needs; from the destructor, you
call Halt to make sure that your board stops acquiring data whenever your class instance gets destructed:

TachyonADC::TachyonADC()
: mSourceCh( 0 ),
mSampleBlockSize( 0 ),
mSamplingRate( 0 )
{
BEGIN_PARAMETER_DEFINITIONS
"Source int SourceCh= 64 64 1 128 "
"// this is the number of digitized channels",
"Source int SampleBlockSize= 16 5 1 128 "
"// this is the number of samples transmitted at a time",
"Source int SamplingRate= 128 128 1 4000 "
"// this is the sample rate",
END_PARAMETER_DEFINITIONS
}

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

ADC Initialization
Your Preflight function will check whether the board works with the parameters requested, and
communicate the dimensions of its output signal:

void TachyonADC::Preflight( const SignalProperties&,


SignalProperties& outputProperties ) const
{
if( TACHYON_NO_ERROR != TachyonStart( Parameter( "SamplingRate" ),
Parameter( "SourceCh" ) ) )
bcierr << "SamplingRate and/or SourceCh parameters are not compatible"
<< " with the A/D card"
<< endl;
TachyonStop();
outputProperties = SignalProperties( Parameter( "SourceCh" ),
Parameter( "SampleBlockSize" ),
SignalType::int16 );
}

Here, the last argument of the SignalProperties constructor determines not only the type of the signal
propagated to the BCI2000 filters but also the format of the dat file written by the source module.

You might want to write SignalType::int32 or SignalType::float32 instead if your data


acquisition hardware acquires data in one of those formats.

The actual Initialize function will only be called if Preflight did not report any errors. Thus, you
may skip any further checks, and write

void TachyonADC::Initialize( const SignalProperties&, const


SignalProperties& )
{
mSourceCh = Parameter( "SourceCh" );
mSampleBlockSize = Parameter( "SampleBlockSize" );
mSamplingRate = Parameter( "SamplingRate" );
TachyonStart( mSamplingRate, mSourceCh );
}

Balancing the TachyonStart call in the Initialize function, your Halt function should stop all
asynchronous activity that your ADC code initiates:

void TachyonADC::Halt()
{
TachyonStop();
}

Data Acquisition
Note that the Process function may not return unless the output signal is filled with data, so it is crucial
that TachyonWaitForData is a blocking function. (If your card does not provide such a function, and
you need to poll for data, don't forget to call Sleep( 0 ) inside your polling loop to avoid tying up the
CPU.)

void TachyonADC::Process( const GenericSignal&, GenericSignal& outputSignal


)
{
int valuesToRead = mSampleBlockSize * mSourceCh;
short* buffer;
if( TACHYON_NO_ERROR == TachyonWaitForData( &buffer, valuesToRead ) )
{
int i = 0;
for( int channel = 0; channel < mSourceCh; ++channel )
for( int sample = 0; sample < mSampleBlockSize; ++sample )
outputSignal( channel, sample ) = buffer[ i++ ];
}
else
bcierr << "Error reading data" << endl;
}

Adding the SourceFilter


Most measurement equipment comes with hardware filters that allow you to filter out line noise. For
equipment that does not offer such an option, consider adding the SourceFilterto your data acquisition
module as described here.

Finished
You are done! Use your TachyonADC.cpp to replace the GenericADC descendant in an existing source
module, add the TachyonADC.lib shipped with your card to the project, compile, and link.

Programming Tutorial:Implementing a Signal


Processing Filter
This tutorial shows you how to derive a new filter class from GenericFilter, how to check
preconditions, initialize your filter, and process data. It will also show you how to visualize the output signal
of the filter and present it to the operator user.

Contents
[hide]

 1 A simple low pass filter


 2 The filter skeleton
 3 The Process function
 4 The Initialize member function
 5 The Preflight function
 6 Constructor and destructor
 7 Filter instantiation
 8 Visualizing filter output

A simple low pass filter


We want to implement a low pass filter with a time constant (given in units of a sample's duration), a

sequence as input and a sequence as output (where is a sample index proportional to


time), and obeying

The filter skeleton


The resulting filter class is to be called LPFilter. We create two new files, LPFilter.h,
and LPFilter.cpp, and put a minimal filter declaration into LPFilter.h:

#ifndef LP_FILTER_H
#define LP_FILTER_H

#include "GenericFilter.h"

class LPFilter : public GenericFilter


{
public:
LPFilter();
~LPFilter();

void Preflight( const SignalProperties&, SignalProperties& ) const;


void Initialize( const SignalProperties&, const SignalProperties& );
void Process( const GenericSignal&, GenericSignal& );
};
#endif // LP_FILTER_H

Into LPFilter.cpp we put the lines

#include "PCHIncludes.h" // Make the compiler's Pre-Compiled Headers


feature happy
#pragma hdrstop
#include "LPFilter.h"

#include "MeasurementUnits.h"
#include "BCIError.h"
#include <vector>
#include <cmath>

using namespace std;

The Process function


When implementing a filter, a good strategy is to begin with the Process function, and to consider the
remaining class member functions mere helpers, mainly determined by the code of Process. So we
convert the filter prescription into the Process code, introducing member variables ad hoc , ignoring
possible error conditions, and postponing efficiency considerations:

void LPFilter::Process( const GenericSignal& Input, GenericSignal& Output )


{
// This implements the prescription's second line for all channels:
for( int channel = 0; channel < Input.Channels(); ++channel )
{
for( int sample = 0; sample < Input.Elements(); ++sample )
{
mPreviousOutput[ channel ] *= mDecayFactor;
mPreviousOutput[ channel ] +=
Input( channel, sample ) * ( 1.0 - mDecayFactor );
Output( channel, sample ) = mPreviousOutput[ channel ];
}
}
}

The Initialize member function


As you will notice when comparing Process to the equations above, we introduced member variables
representing these sub-expressions:

We introduce these members into the class declaration, adding the following lines after
the Process declaration:
private:
double mDecayFactor;
std::vector<double> mPreviousOutput;

The next step is to initialize these member variables, introducing filter parameters as needed. This is done
in the Initialize member function -- we write it down without considering possible error conditions:

void LPFilter::Initialize( const SignalProperties& Input,


const SignalProperties& Output )
{
// This will initialize all elements with 0,
// implementing the first line of the filter prescription:
mPreviousOutput.clear();
mPreviousOutput.resize( Input.Channels(), 0 );

double timeConstant = Parameter( "LPTimeConstant" );


mDecayFactor = ::exp( -1.0 / timeConstant );
}

Now this version is quite inconvenient for a user going to configure our filter -- the time constant is given in
units of a sample's duration, resulting in a need to re-configure each time the sampling rate is changed. A
better idea is to let the user choose whether to give the time constant in seconds or in sample blocks. To
achieve this, there is a utility classMeasurementUnits that has a member ReadAsTime(), returning
values in units of sample blocks which is the natural time unit in a BCI2000 system. Writing a number
followed by an "s" will allow the user to specify a time value in seconds; writing a number without the "s" will
be interpreted as sample blocks. Thus, our user friendly version ofInitialize reads

void LPFilter::Initialize( const SignalProperties&, const SignalProperties&


)
{
mPreviousOutput.clear();
mPreviousOutput.resize( Input.Channels(), 0 );
// Get the time constant in units of a sample block's duration:
double timeConstant = MeasurementUnits::ReadAsTime( Parameter(
"LPTimeConstant" ) );
// Convert it into units of a sample's duration:
timeConstant *= Parameter( "SampleBlockSize" );
mDecayFactor = ::exp( -1.0 / timeConstant );
}

The Preflight function


Up to now, we have not considered any error conditions that might occur during execution of our filter code.
Scanning through the Process and Initialize code, we identify a number of implicit assumptions:

1. The time constant is not zero -- otherwise, a division by zero will occur.

2. The time constant is not negative -- otherwise, the output signal is no longer guaranteed to be
finite, and a numeric overflow may occur.

3. The output signal is assumed to hold at least as much data as the input signal contains.

The first two assumptions may be violated if a user enters an illegal value into the LPTimeConstant
parameter; we need to make sure that an error is reported, and no code is executed that depends on these
two assumptions. For the last assumption, we request an appropriate output signal from
the Preflight function. Thus, the Preflight code reads

void LPFilter::Preflight( const SignalProperties& Input,


SignalProperties& Output ) const
{
double LPTimeConstant = MeasurementUnits::ReadAsTime( Parameter(
"LPTimeConstant" ) );
LPTimeConstant *= Parameter( "SampleBlockSize" );
// The PreflightCondition macro will automatically generate an error
// message if its argument evaluates to false.
// However, we need to make sure that its argument is user-readable
// -- this is why we chose a variable name that matches the parameter
// name.
PreflightCondition( LPTimeConstant > 0 );
// Alternatively, we might write:
if( LPTimeConstant <= 0 )
bcierr << "The LPTimeConstant parameter must be greater 0" << endl;

// Request output signal properties:


Output = Input;
}

Constructor and destructor


Because we do not explicitly acquire resources, nor perform asynchronous operations, there is nothing to
be done inside the LPFilter destructor . Our constructor will contain initializers for the members we
declared, and a BCI2000 parameter definition for LPTimeConstant. Specifying the empty string for both low
and high range tells the framework not to perform an automatic range check on that parameter.

LPFilter::LPFilter()
: mDecayFactor( 0 ),
mPreviousOutput( 0 )
{
BEGIN_PARAMETER_DEFINITIONS
"Filtering float LPTimeConstant= 16s"
" 16s % % // time constant for the low pass filter in blocks or
seconds",
END_PARAMETER_DEFINITIONS
}

LPFilter::~LPFilter()
{
}

Filter instantiation
To have our filter instantiated in a signal processing module, we add a line containing a Filter statement
to the module's PipeDefinition.cpp. This statement expects a string parameter which is used to
determine the filter's position in the filter chain. If we want to use the filter in the AR Signal Processing
module, and place it after theSpatialFilter, we add

#include "LPFilter.h"
...
Filter( LPFilter, 2.B1 );

to the file SignalProcessing/AR/PipeDefinition.cpp. Now, if we compile and link the AR Signal


Processing module, we get an "unresolved external" linker error that reminds us to add
our LPFilter.cpp to that module's project.

Visualizing filter output


Once our filter has been added to the filter chain, the BCI2000 framework will automatically create a
parameter VisualizeLPFilter that is accessible under Visualize->Processing Stages in the operator
module's configuration dialog. This parameter allows the user to view the LPfilter's output signal in a
visualization window. In most cases, this visualization approach is sufficient. For the sake of this tutorial,
however, we will disable automatic visualization, and implement our own signal visualization.

To disable automatic visualization, we override


the GenericFilter::AllowsVisualization() member function to return false. In addition, to
present the LPFilter's output signal in an operator window, we introduce a member of
type GenericVisualization into our filter class, adding

#include "GenericVisualization.h"
...
class LPFilter : public GenericFilter
{
public:
...
virtual bool AllowsVisualization() const { return false; }

private:
...
GenericVisualization mSignalVis;
};
...

GenericVisualization's constructor takes a string-valued visualization ID as a parameter; we need


to get a unique ID in order to get our data routed to the correct operator window. Given the circumstances,
a string consisting of the letters "LPFLT" appears unique enough, so we change
the LPFilter constructor to read

LPFilter::LPFilter()
: mDecayFactor( 0 ),
mPreviousOutput( 0 ),
mSignalVis( "LPFLT" )
{
BEGIN_PARAMETER_DEFINITIONS
"Filtering float LPTimeConstant= 16s"
" 16s % % // time constant for the low pass filter in blocks or
seconds",
"Visualize int VisualizeLowPass= 1"
" 1 0 1 // visualize low pass output signal (0=no, 1=yes)",
END_PARAMETER_DEFINITIONS
}

In Initialize, we add

mSignalVis.Send( CfgID::WindowTitle, "Low Pass" );


mSignalVis.Send( CfgID::GraphType, CfgID::Polyline );
mSignalVis.Send( CfgID::NumSamples, 2 * Parameter( "SamplingRate" ) );

Finally, to update the display in regular intervals, we add the following at the end of Process:

if( Parameter( "VisualizeLowPass" ) == 1 )


mSignalVis.Send( Output );
We might also send data to the already existing task log memo window, adding another member

GenericVisualization mTaskLogVis;

initializing it with

LPFilter::LPFilter()
: ...
mTaskLogVis( SourceID::TaskLog )
{
...
}

and, from inside Process, writing some text to it as in

if( output( 0, 0 ) > 10 )


{
mTaskLogVis << "LPFilter: (0,0) entry of output exceeds 10 and is "
<< output( 0, 0 )
<< endl;
}

Programming Tutorial:Working with the FieldTrip


buffer
One of the user-contributed Signal Processing filters is the FieldTripBuffer. The FieldTripBuffer allows you
to use BCI2000 in conjunction with MATLAB, just as with theMatlabFilter. The difference between the two
interfaces to MATLAB is that the MatlabFilter causes some MATLAB code to be executed in the BCI2000
pipeline, i.e. BCI2000 stays in control over the timing and the MATLAB session only sees a small fragment
of the data. That also forces you to write your MATLAB code to use recursive/incremental updating
schemes. The FieldTripBuffer interface to MATLAB allows you to control the timing from within MATLAB
and read arbitrary sections of data from the ongoing data stream as if it were a continuously growing file,
giving more flexibility in the MATLAB code that you can employ.

The advantage of the FieldTripBuffer interface is that you have all control in MATLAB that you are used to.
You can write your MATLAB code for offline-analysis (i.e. reading data from a file) and apply exactly the
same code to online analysis (i.e. reading from BCI2000). Of course for the online analysis to make some
sense, your analysis script has to be meaningful and has to work with relatively small data fragments (e.g.
one second or less), otherwise the MATLAB code would not really run in real-time. Another interesting
feature is that in MATLAB you can use the profiler (type "help profile") to determine which parts of your
code take a long time to execute and speed those parts up.
The remainder of this page gives an example of how to get the data into MATLAB, plot the data using
standard MATLAB code, and how to close the BCI loop by writing an event back to BCI2000. The example
below does not do any useful processing, it is up to you to decide how you want to process the data in
MATLAB. A number of realtime applications are included in the realtime module of the FieldTrip toolbox.
Additional documentation for that can be found on the FieldTrip website, under development->realtime.

Getting the data in Matlab


The FieldTrip buffer is a multi-threaded and network transparent buffer that allows data to be streamed to it
by BCI2000, while at the same time allowing a seperate MATLAB session on the same or another
computer to read data from the buffer for analysis. Besides writing the data, BCI2000 also writes the
changed status variables as events.

To use the FieldTrip buffer, you start BCI2000 with the FieldTripBuffer as the Signal Processing application.
Subsequently you start MATLAB yourself, i.e. your MATLAB session is a normal standalone application.
You should have a recent copy of the FieldTrip toolbox installed, or at least a copy of the FieldTrip fileio
module. The FieldTrip toolbox and its components are available for download
from http://www.ru.nl/neuroimaging/fieldtrip. Please make sure that the correct version of the fileio module
is on your MATLAB search path.

Subsequently you can do something like the following code below. You should be able to copy and paste
the code into the MATLAB command window and get a real-time updating MATLAB figure with the data
from BCI2000.

filename = 'buffer://localhost:1972';

% read the header for the first time to determine number of channels and
sampling rate
hdr = read_header(filename, 'cache', true);

count = 0;
prevSample = 0
blocksize = hdr.Fs;
chanindx = 1:hdr.nChans;

while true
% determine number of samples available in buffer
hdr = read_header(filename, 'cache', true);

% see whether new samples are available


newsamples = (hdr.nSamples*hdr.nTrials-prevSample);

if newsamples>=blocksize
% determine the samples to process
begsample = prevSample+1;
endsample = prevSample+blocksize ;

% remember up to where the data was read


prevSample = endsample;
count = count + 1;
fprintf('processing segment %d from sample %d to %d\n', count,
begsample, endsample);

% read data segment from buffer


dat = read_data(filename, 'header', hdr, 'begsample', begsample,
'endsample', endsample, 'chanindx', chanindx);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%
% subsequently the data can be processed, here it is only plotted

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%

% create a matching time-axis


time = (begsample:endsample)/hdr.Fs;

% plot the data just like a standard FieldTrip raw data strucute
plot(time, dat);

% ensure tight axes


xlim([time(1) time(end)]);

% force Matlab to update the figure


drawnow

end % if new samples available


end % while true

Closing the loop, writing a control signal from Matlab to


BCI2000
To close the loop, you have to write the control signal back to BCI2000. Since the FieldTrip buffer can only
hold raw data, the control signal cannot be written as data. Instead, the control signal is written as an event.
This is easily demonstrated if you run the Feedback Demo Task.

event.type = 'Signal';
event.sample = 1;
event.offset = 0;
event.duration = 1;
event_up = event;
event_up.value = 1;
event_down = event;
event_down.value = -1;
event_null = event;
event_null.value = 0;

You can write the events to the buffer according to the following example code:

filename = 'buffer://localhost:1972';

ft_write_event(filename, event_up); % ... the cursor will move up ...

ft_write_event(filename, event_down); % ... the cursor will move down ...

ft_write_event(filename, event_null); % ... the cursor will stay at a


constant value ...

The control signal in BCI2000 remains at a constant value as long as you don't write another event with
another control signal.

Programming Tutorial:Implementing an Input Logger


In this tutorial, you will learn how to implement a BCI2000 Input Logger component. Here, Input
Logging refers to recording the state of input devices, such as joysticks, keyboards, or mice, into BCI2000
state variables.

Contents
[hide]

 1 Overview
 2 Implementation
o 2.1 Device API
o 2.2 Event Interface
o 2.3 Thread Interface
o 2.4 EnvironmentExtension Class
 3 Finished
 4 See also

Overview
In BCI2000, input logging can be done with per-sample resolution. Typically, BCI2000 data acquisition,
signal processing, and application feedback code runs in a pipesynchronously, being called once
per BCI2000 sample block, and cannot detect state changes in an input device more frequently than that.

To support input logging with per-sample resolution, BCI2000 allows code to post so-
called events asynchronously from a separate thread, which are time-stamped internally and matched
against the current data block's time stamp in order to associate them with individual samples.

In this tutorial, we will discuss how to implement input Logging for a device by polling its state in regular
intervals. Generally, relying on OS events to detect changes in device state is preferred over polling;
however, whether and how device state is available via OS events depends strongly on the device's driver
software, and it is thus difficult to provide a valid example. Readers interested in input logging via OS
events should read this tutorial first, and then proceed to the key logger component's source code for a
non-polling example.

Implementation
An input logger component consists of a combination of a few existing software components, which are all
provided by BCI2000 except the device API itself.

Device API
The device API provides functions that allow to read, or manipulate, the state of the input device. Typically,
it consists of a library (DLL), and an associated header file.

For the sake of this tutorial, we will assume that the device has the shape of thumb wheel, and has one
continuous degree of freedom. Its header file, ThumbWheel.h, provides a C-style interface:

#define THUMB_WHEEL_MAX_POS 32767


enum { ThumbOK = 0, ThumbBusy, ThumbUnavailable };
int ThumbWheelInit();
int ThumbWheelGetPos();

In order to connect to the thumb wheel, we call ThumbWheelInit(), receiving ThumbOK if everything is
fine. The ThumbWheelGetPos() function will return the wheel's current position, as an integer between
zero and THUMB_WHEEL_MAX_POS.
Event Interface
Using the BCI2000 event interface, device state may be written into BCI2000 states asynchronously. We
will use the event interface to record the wheel's position into a state called ThumbWheelPos, writing

#include "BCIEvent.h"
...
bcievent << "ThumbWheelPos " << ThumbWheelGetPos();

Thread Interface
In order to observe the wheel's state independently of BCI2000's processing of data blocks, we create a
thread that polls wheel state in regular intervals. We will use BCI2000'sOSThread class to implement that
separate thread.

#include "OSThread.h"
#include "ThumbWheel.h"

class ThumbThread : public OSThread


{
ThumbThread()
{}
virtual ~ThumbThread()
{}
virtual int Execute()
{
if( ThumbOK == ThumbWheelInit() )
{
int lastWheelPos = -1;
while( !IsTerminating() )
{
Sleep( 1 );
int curWheelPos = ThumbWheelGetPos();
if( curWheelPos != lastWheelPos )
bcievent << "ThumbWheelPos " << ThumbWheelGetPos();
lastWheelPos = curWheelPos;
}
}
return 0;
}
};

Note that we avoid sending events if there is no change in position. Otherwise, the event queue will grow
very large, increasing overall processing and memory load even if there is no information to record.
EnvironmentExtension Class
The EnvironmentExtension Class is a base class for BCI2000 components ("extensions") that are
not filters. Such extensions do not process signals but still have access toBCI2000 parameters and state
variables, and are notified of system events such as Preflight, Initialize, and StartRun.

This is the extension's header file:

#ifndef THUMBWHEEL_LOGGER_H
#define THUMBWHEEL_LOGGER_H

#include "Environment.h"
#include "ThumbThread.h"

class ThumbWheelLogger : public EnvironmentExtension


{
public:
ThumbWheelLogger()
: mLogThumbWheel( false ),
mpThumbWheelThread( NULL )
{}
virtual ~ ThumbWheelLogger()
{}
virtual void Publish();
virtual void Preflight() const;
virtual void Initialize();
virtual void StartRun();
virtual void StopRun();
virtual void Halt();

private:
bool mLogThumbWheel;
ThumbWheelThread* mpThumbWheelThread;
};
#endif // THUMBWHEEL_LOGGER_H

In our extension component's Publish() member function, we test for a parameter LogThumbWheel,
and only request the "ThumbWheelPos" state variable if logging is actually enabled.
The LogThumbWheel parameter will be available if the module has been started up with --
LogThumbWheel=1 specified on the command line; this way, logging may be enabled and disabled, with
no state variable allocated when logging is disabled. Note that we request the LogThumbWheel parameter
even if it already exists; this has the effect of providing appropriate auxiliary information about that
parameter, i.e. its section, type, and comment fields.
void ThumbWheelLogger::Publish()
{
if( OptionalParameter( "LogThumbWheel" ) > 0 )
{
BEGIN_PARAMETER_DEFINITIONS
"Source:Log%20Input int LogThumbWheel= 1 0 0 1 "
" // record thumb wheel to state (boolean)",
END_PARAMETER_DEFINITIONS

BEGIN_EVENT_DEFINITIONS
"ThumbWheelPos 15 0 0 0",
END_EVENT_DEFINITIONS
}
}

From the Preflight() member function, we check whether the thumb wheel is available:

void ThumbWheelLogger::Preflight() const


{
if( OptionalParameter( "LogThumbWheel" ) > 0 )
if( ThumbOK != ThumbWheelInit() )
bcierr << "ThumbWheel device unavailable" << endl;
}

In Initialize(), we read the LogThumbWheel parameter's value into a class member:

void ThumbWheelLogger::Initialize()
{
mLogThumbWheel = ( OptionalParameter( "LogThumbWheel" ) > 0 );
}

From the component's StartRun() member function, we instantiate the thumb wheel thread class
declared above, thereby running its Execute() member in a new thread:

void ThumbWheelLogger::StartRun()
{
if( mLogThumbWheel )
{
mpThumbWheelThread = new ThumbWheelThread;
mpThumbWheelThread->Start();
}
}

Mirroring StartRun(), StopRun() disposes of the thumbwheel logging thread.

void ThumbWheelLogger::StopRun()
{
if( mpThumbWheelThread != NULL )
{
OSEvent terminateEvent;
mpThumbWheelThread->Terminate( &terminateEvent );
terminateEvent->Wait();
delete mpThumbWheelThread;
mpThumbWheelThread = NULL;
}
}

We also forward StopRun() functionality to the Halt() member to ensure appropriate halting of
asynchronous activity:

void ThumbWheelLogger::Halt()
{
StopRun();
}

Finally, to make sure there exists an object of our ThumbWheelLogger class, we use
the Extension macro at the top of its .cpp file:

Extension( ThumbWheelLogger );

Finished
Now, when we add the ThumbWheelLogger.cpp file to a source module, then the module will contain
an object of our newly created class, and it will listen to the --LogThumbWheel=1 command line option.

Programming Reference:StimulusTask Class


Contents

[hide]

 1 Location
 2 Synopsis
 3 Introduction
o 3.1 Presentation of stimuli
o 3.2 Grouping of stimuli
o 3.3 Sequencing
o 3.4 Classification vs Target Selection
 3.4.1 Specifying Stimulus-Target Relations
 3.4.2 Accumulation and Combination of Evidence
o 3.5 Using a Different Classifier with StimulusTask
 3.5.1 Converting a Classifier's Output into a Classification Score
 3.5.1.1 Classifier Error Rates
 3.5.1.2 Measures of Significance
 3.5.2 Transmitting Classification Scores
 4 Events
o 4.1 Events Summary
o 4.2 Stimulus Code Event
 4.2.1 int OnNextStimulusCode
o 4.3 Events forwarded from the GenericFilter interface
 4.3.1 OnPreflight(SignalProperties (r))
 4.3.2 OnInitialize(SignalProperties (r))
 4.3.3 OnStartRun
 4.3.4 OnStopRun
 4.3.5 OnHalt
o 4.4 Phase transition Events
 4.4.1 OnPreSequence
 4.4.2 OnSequenceBegin
 4.4.3 OnSequenceEnd
 4.4.4 OnStimulusBegin(int stimulusCode (r))
 4.4.5 OnStimulusEnd(int stimulusCode (r))
 4.4.6 OnPostRun
o 4.5 Input Events
 4.5.1 DoPreRun(GenericSignal (r), bool doProgress (rw))
 4.5.2 DoPreSequence(GenericSignal (r), bool doProgress (rw))
 4.5.3 DoStimulus(GenericSignal (r), bool doProgress (rw))
 4.5.4 DoISI(GenericSignal (r), bool doProgress (rw))
 4.5.5 DoPostSequence(GenericSignal (r), bool doProgress (rw))
 4.5.6 DoPostRun(GenericSignal (r), bool doProgress (rw))
o 4.6 Classification Events
 4.6.1 OnClassInput(stimulusCode (r), GenericSignal (r))
 4.6.2 Target* OnClassResult(ClassResult (r))
 5 Properties
o 5.1 AssociationMap Associations (rw)
o 5.2 Target* AttendedTarget (rw)
o 5.3 GUI::GraphDisplay Display (r)
o 5.4 ostream AppLog, AppLog.File, AppLog.Screen (w)
o 5.5 RandomGenerator RandomNumberGenerator (rw)
 6 Methods
o 6.1 DisplayMessage(string)
 7 Parameters
o 7.1 WindowBackgroundColor
o 7.2 PreRunDuration
o 7.3 PostRunDuration
o 7.4 PreSequenceDuration
o 7.5 PostSequenceDuration
o 7.6 StimulusDuration
o 7.7 EarlyOffsetExpression
o 7.8 ISIMinDuration, ISIMaxDuration
o 7.9 InterpretMode
o 7.10 DisplayResults
o 7.11 MinimumEvidence
o 7.12 AccumulateEvidence
 8 States
o 8.1 StimulusCode
o 8.2 StimulusType
o 8.3 StimulusBegin
o 8.4 PhaseInSequence
o 8.5 PauseApplication
 9 Timeline
 10 See also

Location
BCI2000/src/shared/modules/application
Synopsis
The StimulusTask class is a base class for application modules that present a sequence of
stimuli. You do not use objects of type StimulusTask directly; rather, you implement your
own class that inherits from it, and implements specialized behavior building on the base
functionality provided by the StimulusTask class.

The StimulusTask class performs sequencing, and


dispatches GenericFilter::Process() calls to its virtual member functions. Child classes
(descendants) ofStimulusTask implement event handlers by overriding its virtual functions,
and populate the stimulus-target association map, thereby associating stimulus codes with
sets of stimuli, and possible selection targets.

In the BCI2000 core distribution, both the P3SpellerTask and


the StimulusPresentationTask are based on the StimulusTask base class, and implement
their own specialized behavior on top of it.

Introduction
The StimulusTask class is a base class for applications that present stimuli to the user, and
optionally perform selection of a selection target based on a real-time evaluation of the
user's brain signals generated in response to stimulus presentations. Straightforward as this
seems, complication arises from the fact that there is not always a 1-to-1 relation between
stimuli to present, and targets to choose from (e.g., the P3SpellerTask will present rows
and columns of the selection matrix, but the final choice will be a single matrix entry rather
than a row or column). Also, due to the low signal-to-noise ratio in individual evoked
response signals, it is generally necessary to perform multiple stimulus presentations before
a target may be selected with reasonable accuracy.
Through its interface to descendant classes, the StimulusTask base class allows for
efficient implementation of various kinds of such applications. In detail, the following basic
concepts are involved in the interaction between StimulusTask, and a descendant class.
Presentation of stimuli
Individual stimuli are instances of stimulus classes, which derive from a
common Stimulus base class. There are classes for visual and auditory stimuli available.
AStimulusTask descendant class may instantiate any number of stimuli
during OnInitialize(). Each stimulus must be made known to the StimulusTask class by
adding it to a so-called "Association" (see below). The StimulusTask class will then take
care of presenting those stimuli at appropriate points in time, according to user configuration.
Grouping of stimuli
Stimuli are presented as groups called "Associations". Each Association corresponds to a
single StimulusCode value. No Associations exist when
a StimulusCodedescendant's OnInitialize() function is called. As a coding example, the
descendant may add a stimulus to an Association with StimulusCode 23 by calling
SomeStimulusClass* pStimulus = new SomeStimulusClass;
... // set stimulus properties
Associations()[23].Add( pStimulus );

There, a new Association object for StimulusCode 23 will be created if it does not exist.
Stimulus codes are positive integers. They need not be continuous, may be specified in any
order, and chosen as deemed convenient. A zero StimulusCode represents absence of a
stimulus, so 0 cannot be chosen as a StimulusCode index for an association. Still, an
Association object with index 0 may be created, but it will be ignored during stimulus
presentation.

Note that an empty Association object is created on any access to a non-existing index, both
read and write accesses. Thus, you will need to take care to avoid unwanted empty
Associations, i.e., don't use an Association index unless you want that Association to exist. If
an empty Association exists, its StimulusCode may appear in the presentation sequence,
taking up a presentation time slot without any stimulus being presented. In case such
behavior is actually desired, an empty Association may be created by calling

Associations()[index];

Whenever the StimulusCode state becomes nonzero, all stimuli contained in the
corresponding Association are presented simultaneously. An Association may contain a
single stimulus only (e.g., when presenting images in the StimulusPresentation module), or it
may contain multiple stimuli (e.g., matrix speller rows and columns are made up of multiple
stimuli, where each stimulus is a single matrix entry). Also, Associations may contain
different kinds of stimuli (e.g., when presenting both images and audio files with
StimulusPresentation, or when using auditory stimuli to announce rows or columns in the
P3Speller).
Sequencing
Presentation sequences consist of Associations, represented by their StimulusCodes.
The StimulusTask base class does not do any sequencing by itself. Rather, each time
before a presentation happens, the OnNextStimulusCode() function is called, which must
be implemented by a StimulusTask descendant. The descendant class indicates the end of
a sequence by returning zero as a StimulusCode. Further, it indicates the end of a run by
returning a zero StimulusCode twice in a row (i.e., an empty sequence). The information
about existing StimulusCode values will be known by a descendant because it is responsible
for populating the Associations object, but to avoid duplication of information, it may be
appropriate to either

 choose stimulus codes from a continous range 1..N, and obtain N by


calling Associations().size(), or
 iterate over the Associations() object which is a std::map<int, Association>.
In the StimulusPresentation module, sequences may be user-defined, and there exists a 1-
to-1 correspondence between StimulusCodes, and entries in the Stimuli matrix. In the
P3Speller module, sequences are random permutations of an enumeration of all existing
rows and columns, and are chosen randomly at runtime.
Classification vs Target Selection
Brain signals are generated in response to individual stimulus presentations. When used in
conjunction with a StimulusTask application, a signal processing module is supposed to
provide a classification score for individual brain responses, or an average score for a
predefined number of brain responses elicited by presentation of a certain
Association/StimulusCode.
Specifying Stimulus-Target Relations
In general, there is no direct correspondence between stimulus presentations, and possible
selection targets. E.g., in the P3Speller, stimulus presentation is done by highlighting rows
and columns, but an individual matrix entry will be selected rather than a row or column.
Thus, evidence collected in response to presentation or rows and columns needs to be
combined into evidence per selection target before selection can be performed. This
translation of per-presentation evidence into per-target evidence is done automatically by
theStimulusTask class, provided StimulusTask has been given the necessary information
about how stimuli and targets are related to each other.
A Target base class exists for this purpose, which defines a Select method to be
implemented by Target descendants overriding the Target::OnSelect() member function.
As an example, the P3Speller defines a SpellerTarget descendant class which performs
an appropriate action on the Speller object if selected as a target.
The StimulusTask class is given information about stimulus-target relations by the
Association mechanism described above. In addition to Stimulus objects,
theAssociation::Add() function may be called with Target objects as well, creating a link
between presentation of stimuli contained in the Association, and selection of targets
contained in the Association. In many cases, there will be a 1-to-1 relation between stimuli,
and targets, e.g. a P3Speller matrix entry will create both a Stimulus object (so it can be
highlighted), and a Target object (so it can be selected, and perform some action).
However, this is not mandatory -- multiple stimuli may refer to the same target, e.g.,
neighboring P3Speller matrix elements might select the same target in order to make
selection easier for that target; or multiple targets might refer to the same stimulus in a
hierarchic selection scheme. Though it may seem unintuitive at first, no 1-to-N or N-to-1
relations between stimuli and targets need to be specified explicitly in such cases; simply
adding any target related to any stimulus contained in an Association will result in correct
target selection. Individual stimulus-target relations are implicitly contained in the resulting
Associations, and could be recovered by using set operations on the Associations' stimulus
sets, and target sets, but they are not necessary for computing per-target evidence.
Accumulation and Combination of Evidence
Combination of evidence across individual stimulus presentations is also required if one
wants to dynamically choose the number of stimulus presentations that occur before a
selection is made. This may be advantageous if the "quality" of brain responses varies,
which may be due a number of factors, such as position of a target with respect to the user's
eye focus, or the user's attentiveness to the spelling task. In such cases, stimulation
sequences may be repeated indefinitely, and evidence from stimulus presentations may be
collected into per-target evidence, until a certain threshold is reached. Compared to a
system with a fixed number of stimulus repetitions, such a system will improve information
flow (bitrate) by avoiding redundant stimulus presentations which would be wasted time in
case of good signal-to-noise ratio, and by avoiding selection errors in case of bad signal-to-
noise ratio.

In the case of evoked brain potentials (ERPs, e.g., P300 wave), a linear classifier may be
used to classify between the two cases "ERP occurred" vs "no ERP occurred". Quite
favorably, it may be shown that in this case the linear classifier's output is a linear function of
the log-likelihood ratio representing evidence in favor of the "ERP occurred" case, and
against the "no ERP occurred" case. As a result, combined resp. accumulated evidence in
favor of single selection targets can be accurately computed from the classifier's output for
individual brain responses, by using Association information about how stimuli were grouped
in presentation. This allows classification to be performed entirely by the StimulusTask class,
omitting the need for classification code within a descendant class. Still, a descendant class
may implement its own classification by overriding the OnClassResult() function, or it may
modify the default classification result by overriding OnClassResult(), and calling
StimulusTask::OnClassResult() within its own implementation of that function.
Using a Different Classifier with StimulusTask
In general, linear classification may be considered optimal for detection of evoked potential
responses, and the P3SignalProcessing module performs such linear classification.

Still, you might want to use a different classification algorithm in order to classify the brain's
response to a stimulus, e.g. you might want to use oscillatory signal features in addition to or
in place of evoked potentials. In such cases, combination and accumulation of evidence may
not function properly, unless you transform classification output into the kind of data which is
expected by StimulusTask-based applications such as the P3Speller application module.

Converting a Classifier's Output into a Classification Score


As described above, the output of P3SignalProcessing represents a log-likelihood ratio in
favor of the existence of a brain response. Thus, when using StimulusTask with a different
classifier, that classifier's output should be converted into a log-likelihood ratio before
sending it to the application module.
In terms of the probability for the case that no brain response has been observed, the
log-likelihood ratio is defined as
,

where ln stands for the natural logarithm, which is often termed log when provided as a
function in programming environments.

Using the above formula, the output of a binary classifier may be converted into a log-
likelihood ratio by first determining the probability against the existence of a response.
Depending on the nature of your classification algorithm, you may need to do some analysis
by your own in order to determine that probability. In many cases, however, may be
obtained from classifier output by one of the following methods:

Classifier Error Rates

For a classifier with binary output , if the false positive rate is estimated to be , and
the false negative rate is estimated to be , you will have


Measures of Significance
A -value from a statistical test is the probability for the null hypothesis to be true. The null
hypothesis is "there was no ERP", and thus a -value represents the desired probability
against an ERP.

Although a -value may not be directly available, other measures of significance, such as
a -value, may be converted into a -value. Formulae relating the -value to other
measures of significance may be found in statistics textbooks, or web resources.
Transmitting Classification Scores
The StimulusTask class expects to receive a control signal with a single channel, and a
single element. Whenever the StimulusCodeRes state is nonzero, it will treat the single
value of its control signal as an update to the classification score associated with the
StimulusCode sent in the StimulusCodeRes state.

Classification score updates may be sent any time during sequence or post-sequence
(PhaseInSequence state is 2 or 3). Though the order in which classification scores are sent
does not matter, StimulusTask expects to receive exactly one classification result for each
StimulusCode that has been presented since the last classification score update was sent.

Only a single classification score can be sent for each signal data block. If your classifier
operates by comparison of multiple brain responses, rather than each response individually,
you should store classification scores in a stack-like data structure, and send only the top of
the stack at each data block.

Detailed processing of classification score updates depends on


the MinimumEvidence and AccumulateEvidence parameters.
Events
Events Summary
The following table indicates which events exist, and the temporal sequence in which they
occur. Stimulus presentation proceeds in "phases", which are distinct parts of the stimulus
presentation: Each presentation sequence is preceded by a "PreSequence" interval; the
sequence itself consists of alternating "Stimulus" and "Inter Stimulus Intervals (ISIs)"; a
"PostSequence" follows after the sequence.

Events marked with * may occur multiple times in a row. Events given in square brackets
depend on the signal processing module, and may not occur at all; however, when they
occur, their place in the temporal sequence will be as specified in the table.

Progress from one application state to the next will occur according to the sequencing
parameters, or if requested by a handler via its doProgress output argument (see Input
events below).

Sequence of events Phase Typical application behavior

OnPreflight

OnInitialize

OnStartRun display initial message

DoPreRun* PreRun

Loop {

OnNextStimulusCode provide a stimulus code, or 0 to finish run

OnPreSequence determine attended target

DoPreSequence* PreSequence

OnSequenceBegin

Loop {

OnStimulusBegin present stimulus

DoStimulus* Stimulus

OnStimulusEnd hide visual stimulus

provide a stimulus code, or 0 to finish


OnNextStimulusCode sequence

DoISI* ISI

store classification input (copy/free modes


only)
[OnClassInput]* -- this event may occur any time during the
sequence, and during the post sequence
phase

OnSequenceEnd

DoPostSequence* PostSequence

determine selected target (copy/free modes


[OnClassResult]
only)

OnPostRun

DoPostRun* PostRun

OnStopRun display final message

OnHalt

Stimulus Code Event


int OnNextStimulusCode
This event handler is called immediately before stimulus presentation, and determines the
sequence in which stimuli (or associations thereof) are presented. This handler should return
the next element of the current sequence of stimulus codes, or zero to indicate the end of
the sequence. A null sequence indicates the end of a run, i.e. the current run will end
when OnNextStimulusCode returns two zeros in a row.

The OnNextStimulusCode event handler is mandatory for


a StimulusPresentation descendant to implement. Thus, a minimal stimulus presentation
application consists of aStimulusPresentation descendant that

 declares its own additional parameters from its constructor,


 checks those parameters for consistency in its OnPreflight handler,
 populates the Association Map in its OnInitialize handler, and
 provides a sequence of stimulus codes in its OnNextStimulusCode event handler.
Events forwarded from the GenericFilter interface
OnPreflight(SignalProperties (r))
OnInitialize(SignalProperties (r))
These events are forwarded from the
inherited GenericFilter::Preflight and GenericFilter::Initialize events, with
input signal properties as an argument. Within the BCI2000 filter chain,
the StimulusTask class always writes its input signal through to its output signal, so no
output signal properties are provided to these event handlers.
OnStartRun
OnStopRun
OnHalt
These events are forwarded from GenericFilter's StartRun, StopRun, and Halt events.

Phase transition Events


OnPreSequence
OnSequenceBegin
OnSequenceEnd
OnStimulusBegin(int stimulusCode (r))
OnStimulusEnd(int stimulusCode (r))
OnPostRun
These events are triggered by phase transitions during a run, e.g.,
the OnPreSequence event handler is called whenever the phase changes
from PreRun to PreSequence.

The stimulusCode arguments to the OnStimulusBegin and OnStimulusEnd event handlers


contain the stimulus code present in the StimulusCode state variable; the default behavior of
these handlers is to present and conceal the respective stimuli. For the remaining event
handlers of this category, the default behavior is to do nothing.
Input Events
DoPreRun(GenericSignal (r), bool doProgress (rw))
DoPreSequence(GenericSignal (r), bool doProgress (rw))
DoStimulus(GenericSignal (r), bool doProgress (rw))
DoISI(GenericSignal (r), bool doProgress (rw))
DoPostSequence(GenericSignal (r), bool doProgress (rw))
DoPostRun(GenericSignal (r), bool doProgress (rw))
Each call to GenericFilter::Process() is dispatched to one of these event handlers,
depending on the phase in the sequence, with each handler function corresponding to a
phase.
These event handlers will typically not be used by a task class that inherits
from StimulusTask, unless it needs to modify the standard sequencing behavior, such that
it progresses faster or slower from one phase to the next, or subdivides a phase into two or
more sub-phases.

Modifying sequencing behavior is possible through the


handlers' doProgress argument. doProgress will typically be set to false, except for the last
call to the handler during the current phase in the sequence. A handler may modify the
application's sequencing behavior by setting the doProgress argument: setting it
from false to true will proceed to the next phase earlier than prescribed by the sequencing
parameters, and setting it to false from true will defer progressing, such that the handler will
be called again, until itsdoProgress argument is actually true on exit.
Classification Events
Target classification is based on the behavior of the P3TemporalFilter signal processing
filter. This filter performs per-stimulus averaging over EEG epochs; once it has acquired the
desired amount of epochs, it sets its output to the average wave form, and
the StimulusCodeRes state to the respective stimulus code. In conjunction with
the LinearClassifier (or other classification filter), this implies that the application module
receives, in its input signal, a single classification value for stimulus
whenever StimulusCodeRes equals .
OnClassInput(stimulusCode (r), GenericSignal (r))
The OnClassInput event handler is called each time a classification value has been received
from the signal processing module. Classification values are stored in a ClassResultobject.
A StimulusPresentation descendant class may take additional action by implementing its
own OnClassInput event handler.

Target* OnClassResult(ClassResult (r))


The StimulusPresentationTask' accumulates classification values in
a ClassResult class, and then calls the OnClassResult event handler once classification
input has been received for all stimulus codes.

The OnClassResult event handler is supposed to determine a selection target from these
classification values. Classification values are provided in a ClassResult object;
theOnClassResult handler returns a pointer to a Target object representing the chosen
selection target, or a null pointer to indicate that no target has been chosen. On return from
theOnClassResult event handler, and in case of a non-null pointer, the target
object's Select() method is then called, which typically results in some action associated
with the target object in question, such as entering a letter into a speller.
The default OnClassResult handler calls AssociationMap::ClassifyTargets() to
translate classification values into selection targets; you may override this behavior by
providing your own handler.

Properties
All properties are protected, i.e. intended for use from descendant classes only.
AssociationMap Associations (rw)
An object of type AssociationMap, representing sets of stimuli and selection targets
associated with a given stimulus code.

Typically, a stimulus presentation application populates the Associations object with stimuli
and targets in its OnInitialize event handler, and relies on
theStimulusPresentationTask default mechanisms for stimulus presentation, and target
classification.

Often, but not always, there is a 1-to-1-to-1 correspondence between stimuli, stimulus
codes, and selection targets; for a detailed discussion of these terms, and how to use the
related objects in your own application, refer to Programming Reference:AssociationMap
Class.
Target* AttendedTarget (rw)
A pointer that refers to a selection target object, or a null pointer. When non-null,
the StimulusType state variable is set based on the AttendedTarget property such
thatStimulusType is set to 1 whenever the current StimulusCode state variable refers to
an Association that contains the specified target.

For a discussion of how stimuli, stimulus codes, and selection targets are related to each
other via Association objects, refer to Programming Reference:AssociationMap
Class#AssociationMap Class.
GUI::GraphDisplay Display (r)
The Display property provides access to the GUI::GraphDisplay object representing the
application module's output window.
ostream AppLog, AppLog.File, AppLog.Screen (w)
Inheriting from ApplicationBase, descendants of StimulusTask have access to
the AppLog, AppLog.File, and AppLog.Screen streams which are members
ofApplicationBase. These streams allow convenient output into an application log file
(AppLog.File), an application log window (AppLog.Screen), and both simultaneously
(AppLog).

RandomGenerator RandomNumberGenerator (rw)


An object of type RandomGenerator which behaves according to the user setting in
the RandomSeed parameter.

Methods
Methods are declared protected, i.e. for use by descendants only.
DisplayMessage(string)
Displays a text message in the application window. This function is provided for convenience
and consistency of appearance; for detailed control over appearance of text messages, add
your own TextField object to the GUI::GraphDisplay represented by the Display property.

Parameters
WindowBackgroundColor
The window's background color, given as an RGB value. For convenience, RGB values may
be entered in hexadecimal notation, e.g. 0xff0000 for red.

PreRunDuration
The duration of the pause preceding the first sequence. Given in sample blocks, or in time
units when immediately followed with 's', 'ms', or similar.
PostRunDuration
Duration of the pause following last sequence. Given in sample blocks, or in time units when
immediately followed with 's', 'ms', or similar.
PreSequenceDuration
Duration of the pause preceding sequences (or sets of intensifications). Given in sample
blocks, or in time units when immediately followed with 's', 'ms', or similar.

In free or copy mode, the PreSequenceDuration and PostSequenceDuration parameters


may not go below twice the value of the StimulusDuration parameters, in order to allow for
presentation of FocusOn and Result announcement stimuli.
PostSequenceDuration
Duration of the pause following sequences (or sets of intensifications). Given in sample
blocks, or in time units when immediately followed with 's', 'ms', or similar.

When used in conjunction with the P3TemporalFilter, this value needs to be larger than
the EpochLength parameter. This allows classification to complete before the next sequence
of stimuli is presented.
StimulusDuration
For visual stimuli, the duration of stimulus presentation. For auditory stimuli, the maximum
duration, i.e. playback of audio extending above the specified duration will be muted. Given
in sample blocks, or in time units when immediately followed with 's', 'ms', or similar.
EarlyOffsetExpression
Allows the specification of an Expression that is constantly monitored during stimulus
presentation. When the value of the Expression transitions from zero to non-zero, the
stimulus is aborted early, even if the StimulusDuration has not yet elapsed. For example, set
this Expression to KeyDown==32 and start your source module with the --
LogKeyboard=1 flag: then the subject will be able to advance the stimulus sequence
manually by pressing the space key.
ISIMinDuration, ISIMaxDuration
Minimum and maximum duration of the inter-stimulus interval. During the inter-stimulus
interval, the screen is blank, and audio is muted.

Actual inter-stimulus intervals vary randomly between minimum and maximum value, with
uniform probability for all intermediate values. Given in sample blocks, or in time units when
immediately followed with 's', 'ms', or similar. Note that temporal resolution is limited to a
single sample block.
InterpretMode
An enumerated value selecting on-line classification of evoked responses:

 0: no target is announced "attended", and no classification is performed;


 1: online or free mode: classification is performed, but no "attended target" is defined;
 2: copy mode: "attended" targets are defined, classification is performed.
DisplayResults
Switches result display of copy/free spelling on or off. In the P3Speller,
setting DisplayResults to 'off' will disable execution of all speller commands (such as
switching matrices) as well.
MinimumEvidence
NOTE: If you are using your own classifier, this feature will not work properly unless your
classifier's output matches certain criteria. Make sure to read these notes on how to use a
different classifier.

By default, target selection is performed without considering the actual amount of evidence
that favors the selected target over other targets. Although the selected target will always be
a target with maximum classification score (i.e., evidence), other targets may have the same
or a similar score. It may be useful to omit classification in such situations altogether, by
specifying a minimum amount of evidence that must exist in favor of the selected target,
when compared to the next-best target. When used together with
theAccumulateEvidence option, this allows to dynamically control the number of stimulus
presentations, by simply repeating stimulus sequences until a sufficient amount of evidence
has been collected.

Setting MinimumEvidence to 0 or to a negative number will result in default behavior, i.e.


there will be a target selection each time classification scores are received from the
SignalProcessing module. For values greater 0, the amount of selection errors will become
smaller as the value of MinimumEvidence is increased; this increases the amount of
information contained in each selection. At the same time, it becomes more and more
unlikely that a selection will occur at all within a certain amount of time; this decreases the
amount of information transmitted per time (information flow, or bitrate). In between, a
certain value will correspond to an optimal compromise between selection errors, and
selection duration. At this point, the flow of information is maximized.
The meaning of the actual number entered into the MinimumEvidence parameter is relative
to the amount of within-class variance present in the classification score. An evidence of 0
means a 50:50 chance for correct classification. Increasing the evidence value by two
standard deviations corresponds to an improvement by a factor of roughly 88:12, four
standard deviations correspond to (88:12)^2=(98:2) ... etc, approaching perfect classification
as evidence increases towards infinity.

In classifier training, classifier weights may be normalized such that within-class variance is
1 (this is done by recent versions of the P300Classifier tool). In this case, you may use the
following equations to convert between the MinimumEvidence parameter , and the correct
classification chance :

For large , this relationship may be approximated and expressed in terms of error
probability :

Thus, the evidence value roughly corresponds to twice the number of leading zeros in the
desired error probability, if classifier weights are normalized. Some values are provided in
the following table:

Selection Error Evidence

5% 3

1% 4.6

0.5% 5.3

0.1% 6.9

0.05% 7.6

0.01% 9.2

AccumulateEvidence
By default, only those classification scores are used which have been received from the
signal processing module immediately prior to classification. When AccumulateEvidence is
set, classification scores are accumulated until a selection is actually performed. Typically,
accumulated classification scores will have higher evidence values, such that a selection
threshold set with MinimumEvidence will be eventually crossed after scores have been
accumulated for some time.
This allows for dynamically choosing the number of stimulus repetitions in a P300 paradigm,
by setting the number of stimulus repetitions to 1, and setting
the MinimumEvidenceparameter to a value greater zero.

In addition, accumulated overall evidence will not increase if there is no consistent evidence
in favor of a certain target. Thus, it is possible to operate a P300 BCI in a quasi-
asynchronous mode by using AccumulateEvidence, and choosing
a MinimumEvidence value that is large enough to make accidental selection unlikely. In this
configuration, no selection will be made unless the BCI user is actually concentrating on a
target for a number of stimulus presentations, resulting in consistently accumulating
evidence for that target.
NOTE: If you are using your own classifier, this feature will not work properly unless your
classifier's output matches certain criteria. Make sure to read these notes on how to use a
different classifier.

States
StimulusCode
The numerical ID of the stimulus being presented (16 bit).
StimulusType
This state is 1 during presentation of an attended stimulus, and 0 otherwise. The notion of an
"attended" stimulus requires data recording in copy mode.
StimulusBegin
This state is 1 during the first block of stimulus presentation, and 0 otherwise.
PhaseInSequence
This state is 1 during pre-sequence, 2 during sequence and 3 during post-sequence
(see Timeline).
PauseApplication
While this state is set to 1, no task processing occurs, i.e. the task is paused, and may be
resumed by setting PauseApplication to 0.

Timeline
Programming Reference:Stimulus Class
Contents
[hide]

 1 Stimulus Class
o 1.1 Location
o 1.2 Synopsis
o 1.3 Properties
 1.3.1 int Tag (rw)
o 1.4 Methods
 1.4.1 Present()
 1.4.2 Conceal()
o 1.5 Events
 1.5.1 OnPresent()
 1.5.2 OnConceal()
o 1.6 Descendants
 2 SetOfStimuli Class
o 2.1 Location
o 2.2 Synopsis
o 2.3 Methods
 2.3.1 Add(Stimulus pointer)
 2.3.2 Remove(Stimulus pointer)
 2.3.3 Clear()
 2.3.4 DeleteObjects()
 2.3.5 bool Contains(Stimulus pointer)
 2.3.6 bool Intersects(SetOfStimuli)
 2.3.7 Present()
 2.3.8 Conceal()
 3 See also

Stimulus Class
Location
src/shared/modules/application/stimuli

Synopsis
The Stimulus class is a virtual base class which defines an event handling interface for stimuli. There, a
"Stimulus" is defined as "an object that can present (and possibly conceal) itself."

The Stimulus class is purely virtual; descendant classes need to implement


its OnPresent() and OnConceal() event handlers. An application uses the
publicStimulus::Present() and Stimulus::Conceal() functions to control its behavior; these, in
turn, call the virtual event handlers.

The SetOfStimuli class allows to define sets of stimuli, and to present or conceal all of the stimuli it
contains.

Properties
int Tag (rw)
An arbitrary integer that may be used to encode information about a given stimulus (e.g., its corresponding
row in a configuration matrix).

Methods
Present()
Prompts a stimulus to present itself by calling its OnPresent() event handler.

Conceal()
Prompts a stimulus to conceal itself by calling its OnConceal() event handler.

Events
OnPresent()
In its OnPresent event handler, a stimulus is supposed to present itself, e.g., to make itself visible, play
itself if it is a sound or a movie, or highlight itself if it is a P300 matrix element.

OnConceal()
In its OnConceal event handler, a stimulus is supposed to conceal itself, e.g., make itself invisible, or
switch back to normal mode. For non-visual stimuli, this handler is typically empty.
This event is called Conceal rather than Hide because "Hide" is already used as a name for
a GraphObject's function that makes it invisible.

Descendants
The Stimulus class is parent to a class hierarchy containing a number of auditory and visual stimuli:

Stimulus |-> VisualStimulus |-> ImageStimulus


| |-> TextStimulus
|-> AudioStimulus
|-> SpeechStimulus
|-> SoundStimulus

SetOfStimuli Class
Location
SetOfStimuli is declared in the Stimulus class header file.

Synopsis
SetOfStimuli is a helper class that allows to prompt presentation, concealing, and destruction of a
group of stimuli.

Methods
Add(Stimulus pointer)
Adds a stimulus object to the set.

Remove(Stimulus pointer)
Removes a given stimulus object from the set; nothing happens when the specified stimulus is not a
member of the set.

Clear()
Clears the set. Stimuli that were represented in the set in form of pointers are unaffected.

DeleteObjects()
Deletes all stimulus objects that are in the set, and clears the set. To avoid dangling pointers, and multiple
deletion, you should make sure that the set's members are not part of any other SetOfStimuli by the
time you call SetOfStimuli::DeleteObjects().

bool Contains(Stimulus pointer)


Returns true if the specified stimulus object is part of the set, and false otherwise.
bool Intersects(SetOfStimuli)
Returns true if any of the elements of the specified set is part of the set on which Intersects is called,
and false otherwise.

Present()
Prompts each element of the set to present itself by calling its Present() method.

Conceal()
Prompts each element of the set to conceal itself by calling its Conceal() method.

Programming Reference:Target Class


Contents
[hide]

 1 Target Class
o 1.1 Location
o 1.2 Synopsis
o 1.3 Properties
 1.3.1 int Tag (rw)
o 1.4 Methods
 1.4.1 Select()
o 1.5 Events
 1.5.1 OnSelect()
o 1.6 Descendants
 2 SetOfTargets Class
o 2.1 Location
o 2.2 Synopsis
o 2.3 Methods
 2.3.1 Add(Target pointer)
 2.3.2 Remove(Target pointer)
 2.3.3 Clear()
 2.3.4 DeleteObjects()
 2.3.5 bool Contains(Target pointer)
 2.3.6 bool Intersects(SetOfTargets)
 2.3.7 Select()
 3 See also

Target Class
Location
src/shared/modules/application/stimuli

Synopsis
The Target class is a virtual base class which defines an event handling interface for targets. There, a
"Target" is defined as "an object that performs an action when selected."

A Target descendant class will typically override its empty OnSelect() event handlers with a function
that performs an action. An application uses the publicTarget::Select() function to control its
behavior; this function, in turn, calls the virtual event handler.

The SetOfTargets class allows to define sets of targets, and to select all of the targets it contains.

Properties
int Tag (rw)
An arbitrary integer that may be used to encode information about a given target (e.g., its corresponding
row in a configuration matrix).

Methods
Select()
Prompts a target to perform its selection action by calling its OnSelect() event handler.

Events
OnSelect()
In its OnSelect event handler, a target performs an action, e.g. entering a letter into a speller.

Descendants
The Target class is parent to the SpellerTarget and AudioSpellerTarget classes.

SetOfTargets Class
Location
SetOfTargets is declared in the Target class header file.

Synopsis
SetOfTargets is a helper class that provides selection and destruction of a group of stimuli.

Methods
Add(Target pointer)
Adds a target object to the set.
Remove(Target pointer)
Removes a given target object from the set; nothing happens when the specified target is not a member of
the set.

Clear()
Clears the set. Targets that were represented in the set in form of pointers are unaffected.

DeleteObjects()
Deletes all target objects that are in the set, and clears the set. To avoid dangling pointers, and multiple
deletion, you should make sure that the set's members are not part of any other SetOfTargets by the
time you call SetOfTargets::DeleteObjects().

bool Contains(Target pointer)


Returns true if the specified target object is part of the set, and false otherwise.

bool Intersects(SetOfTargets)
Returns true if any of the elements of the specified set is part of the set on which Intersects is called,
and false otherwise.

Select()
Calls each element's Select() method.

Programming Reference:AssociationMap Class


Contents
[hide]

 1 Location
 2 Synopsis
 3 Association Class
o 3.1 Properties
 3.1.1 int StimulusDuration (rw)
 3.1.2 int ISIMinDuration (rw)
 3.1.3 int ISIMaxDuration (rw)
 3.1.4 SetOfStimuli Stimuli (rw)
 3.1.5 SetOfTargets Targets (rw)
o 3.2 Methods
 3.2.1 Clear()
 3.2.2 DeleteObjects()
 3.2.3 Add(Stimulus pointer|Target pointer)
 3.2.4 Remove(Stimulus pointer|Target pointer)
 3.2.5 bool Contains(Stimulus pointer|Target pointer)
 3.2.6 bool Intersects(SetOfStimuli|SetOfTargets)
 3.2.7 Present()
 3.2.8 Conceal()
 3.2.9 Select()
 4 AssociationMap Class
o 4.1 Methods
 4.1.1 bool StimuliIntersect(StimulusCode1, StimulusCode2)
 4.1.2 bool TargetsIntersect(StimulusCode1, StimulusCode2)
 4.1.3 SetOfStimuli TargetToStimuli(Target pointer)
 4.1.4 TargetClassification ClassifyTargets(ClassResult)
 5 ClassResult Class
 6 TargetClassification Class
o 6.1 Methods
 6.1.1 Target pointer MostLikelyTarget()
 7 See also

Location
src/shared/modules/application/stimuli/Association

Synopsis
This page describes four closely related classes declared in a common header file, all related to stimulus
presentation and target classification.

 Association is a class that associates sets of stimuli with sets of targets.


 AssociationMap maps stimulus codes to Associations, and sorts targets according to classification
results given over stimulus codes.
 ClassResult and TargetClassification are auxiliary classes designed as input and output
of AssociationMap's ClassifyTargets() function. ClassResultrepresents accumulated
classification output from signal processing, TargetClassification maps Target pointers to a
selection likelihood value.
Association Class
An object of type Association defines a set consisting of stimuli, an additional set of targets, and a
number of properties associated with a single stimulus code.
A user application based on the StimulusTask class will present stimuli, or groups thereof, in sequence,
and may select targets according to stimulus-related responses it obtains from the signal processing
module.

The Association class serves to associate stimuli with each other, targets with each other, stimuli with
targets, and to store sequencing information about such groups.

Properties
int StimulusDuration (rw)
The duration of stimulus presentation, given in sample blocks.

int ISIMinDuration (rw)


The minimum duration of the inter-stimulus interval, given in sample blocks.

int ISIMaxDuration (rw)


The maximum duration of the inter-stimulus interval, given in sample blocks. When ISIMinDuration is
different from ISIMaxDuration, the StimulusTask base class will choose a random interval between the two
from a uniform distribution.

The StimulusDuration, ISIMinDuration, ISIMaxDuration properties are not used by


the Association class itself but by the StimulusTask application base class to determine application
sequencing.

SetOfStimuli Stimuli (rw)


Provides read/write access to the SetOfStimuli object that stores stimulus pointers within
the Association object.

SetOfTargets Targets (rw)


Provides read/write access to the SetOfTargets object that stores target pointers within
the Association object.

Methods
Clear()
Clears both stimulus and target sets.

DeleteObjects()
Deletes all stimulus and target objects that are part of the association, and clears the association.

NB: You should not call this function for an Association object unless you can be sure that no stimulus
is part of multiple associations. Otherwise, you will risk multiple deallocation of stimulus pointers. Typically,
you will keep track of existing stimuli in a separate SetOfStimuli object, and call that
set's DeleteObjects() member function in order to dispose of stimulus objects.
Add(Stimulus pointer|Target pointer)
Adds a stimulus or target object to the association.

Remove(Stimulus pointer|Target pointer)


Removes a given stimulus object from the association; nothing happens when the specified object is not a
member of the set.

bool Contains(Stimulus pointer|Target pointer)


Returns true if the specified object is part of the association, and false otherwise.

bool Intersects(SetOfStimuli|SetOfTargets)
Returns true if any of the elements of the specified set is part of the association, and false otherwise.

Present()
Prompts each stimulus in the association to present itself by calling its Present() method.

Conceal()
Prompts each stimulus in the association to conceal itself by calling its Conceal() method.

Select()
For each target contained in the association, calls its Select() method.

AssociationMap Class
An AssocationMap links Associations, or groups of stimuli and targets, to stimulus codes. Each
stimulus code maps to a single Association object, and vice versa.

This way, the AssociationMap controls how stimuli are presented in groups, and sequences of groups,
in a stimulus task, and it provides the information required to determine a selected target from a user
response.

Methods
AssociationMap inherits publicly from std::map<int, Association, and provides all its member
functions. In a StimulusTask descendant, the Associations property provides access to the
single AssociationMap object stored inside the StimulusTask object.

Typically, only the subscript operator [] is required to assign stimulus codes to associations:

Associations()[ myStimulusCode ].Add( pMyStimulus );

Similarly, association information may be read when the subscript operator appears on the right hand side
of an assignment:
int stimulusDuration = Associations()[ myStimulusCode ].StimulusDuration();

In case there exists no Association object for a specified stimulus code, an


empty Association object will be created with default properties.

bool StimuliIntersect(StimulusCode1, StimulusCode2)


Returns true if there is at least one stimulus object common to the two stimulus codes
specified, false otherwise.

bool TargetsIntersect(StimulusCode1, StimulusCode2)


Returns true if there is at least one target object common to the two stimulus codes
specified, false otherwise.

SetOfStimuli TargetToStimuli(Target pointer)


Returns an intersection of all sets of stimuli that are associated with the specified target.

TargetClassification ClassifyTargets(ClassResult)
Translates a ClassResult object into TargetClassification object. A ClassResult object
contains classification values per stimulus code; these values are translated into classification values per
target.

ClassResult Class
A mapping of stimulus codes to classification values. Classification values are computed by
the P3TemporalFilter in conjunction with the LinearClassifier, and accumulated by theStimulusTask
class inside a ClassResult object before translated into a target selection.

TargetClassification Class
A TargetClassification object represents per-target classification values. Such an object is returned
from AssociationMap::ClassifyTargets().

Methods
Target pointer MostLikelyTarget()
Returns a pointer to the target associated with the largets classification value, or NULL if there are no
targets stored in the TargetClassification object.

Programming Reference:Speller Class


Contents
[hide]

 1 Location
 2 Synopsis
 3 Speller Class
o 3.1 Methods
 3.1.1 Enter(string)
 3.1.2 Add(SpellerTarget pointer)
 3.1.3 Remove(SpellerTarget pointer)
 3.1.4 DeleteObjects()
 3.1.5 SpellerTarget pointer SuggestTarget(string from, string to)
o 3.2 Events
 3.2.1 OnEnter(string)
 4 SpellerTarget Class
o 4.1 Methods
 4.1.1 SpellerTarget(Speller)
 4.1.2 Select()
o 4.2 Properties
 4.2.1 string EntryText (rw)
 5 See also

Location
src/shared/modules/application/speller

Synopsis
The Speller class provides an abstract interface for "Spellers". A "Speller" is an object that responds to
an OnEnter event, and holds a set of SpellerTarget objects. Holding a set
of SpellerTarget objects allows to suggest a "next target" based on a desired text output, and an
existing text output.

A SpellerTarget is a Target that is linked to a Speller such that selecting the target results in
entering text into the speller.

Speller Class
Methods
Enter(string)
Prompts the speller to react to the information provided in the argument string. The Speller class
interface makes no assumption about the encoding used, i.e. it does not interpret the argument string in
any way.
Add(SpellerTarget pointer)
Adds the specified SpellerTarget to the speller's set of targets.

Remove(SpellerTarget pointer)
Removes the specified SpellerTarget from the speller's set of targets.

DeleteObjects()
Deletes all SpellerTarget objects that are part of the speller's set of targets.

SpellerTarget pointer SuggestTarget(string from, string to)


Returns a pointer to a target from the speller object's set of targets. The suggested target is chosen such
that it minimizes the string distance between the modified "from" argument to the "to" argument after
application of the target's entry text. This is a virtual function, and should be overridden by spellers that use
encoded commands in targets' entry texts.

Events
OnEnter(string)
In its OnEnter event handler, a Speller descendant should perform actions that correspond to entering
the text given as an argument.

SpellerTarget Class
Methods
SpellerTarget(Speller)
When constructing a speller target, specify the Speller object it is associated with.
The SpellerTarget object will add itself to the Speller object's set of targets, and may be deleted
using Speller::DeleteObjects() when appropriate.

Select()
Will enter the text contained in the EntryText property into the speller specified when creating the object.

Properties
string EntryText (rw)
The text entered into the speller object when the target is selected.

Vous aimerez peut-être aussi