User Tutorial:BCI2000Remote
Description
BCI2000Remote is a proxy interface class to the BCI2000 Operator module, and allows to start up, configure, and control BCI2000 from other applications. Internally, it maintains a telnet connection to the Operator module, and sends Operator Scripting commands to control it. However, no knowledge of these scripting commands is required in order to use the BCI2000Remote class from your own application.
BCI2000Remote is most useful when writing applications in C++, or in another language for which bindings to the BCI2000RemoteLib library exist, such as Python, or MATLAB.
Note
BCI2000Remote is a means of interfacing with the BCI2000 Operator module meaning that there are potentially hundreds of ways we leverage this tool to have a meaningful interface with BCI2000. Because of this we will illustrate a few different integrations using different languages. This "How-to" will be separated into three separate sections: Python, C++, and Matlab.
Python Tutorial
- Find your Python folder. If you have an existing Python environment with your code find it using the Sys Library like this:
import sys locate_python = sys.exec_prefix print(locate_python)
-
Locate your BCI2000Remote.py file which should have compiled into your prog folder.
Find where in your Python code you wish to integrate BCI2000. In this example I will show the minimum requirements for integration with BCI2000:
import sys sys.path.append('C:\\BCI2000.x64\\prog') import BCI2000Remote bci = BCI2000Remote.BCI2000Remote() print('Operator path:', bci.OperatorPath) bci.WindowVisible = True bci.WindowTitle = 'Python controlled' bci.SubjectID = 'pysub' bci.Connect() bci.Execute('cd ${BCI2000LAUNCHDIR}') bci.Execute('ADD STATE score 16 0') bci.StartupModules(('SignalGenerator', 'DummySignalprocessing', 'DummyApplication')) bci.Execute('Wait for Connected') bci.LoadParametersRemote('../parms/fragments/amplifiers/amplifier.prm') bci.SetConfig()
Here is how you import BCI2000Remote:
Here is how you instantiate the operator object:
Here is how to customize the operator and connect to it:
Here is how you add your states:
How to specify your signal source, signal processing, and application modules:
How to load a parameter file(you can learn more about parameters here):
File:BCIRemote 55.png
How to connect and issue commands to BCI2000:
-
And finally, how to tell BCI2000 to set states to a value during runtime:
Now that you've added those components to your code all you have to do is run your code. You should see the BCI2000 operator along with the system log, source watcher, and the timing window.
C++ Tutorial
- Open the C++ file or solution you would like to integrate with BCI2000 in Visual Studio, and find where it is located on your computer.
-
Navigate to the program's properties, and make the following changes:
- Under Configuration Properties -> C++ -> General, add the following folders from BCI2000 (64-bit)'s src folder: \core\Operator\BCI2000Remote, \shared\utils\Lib, \shared\config.
- Navigate to Configuration Properties -> Linker -> Input, and add ws2_32.lib to Additional Dependencies.
- Under Configuration Properties -> C++ -> Advanced, add 4996 to the Disable Specific Warnings section.
- Add your Operator executable and the BCI2000RemoteLib dynamic library to the same directory as the program you intend to integrate. This can be found in the prog folder.
- Also, add the following files to the directory with your program and Operator.exe:
- BCI2000Connection.cpp
- BCI2000Connection.h
- BCI2000Remote.cpp
- BCI2000Remote.h
- BCI2000RemoteLib.cpp
- BCI2000RemoteLib.h
- SelfPipe.cpp
- SelfPipe.h
- sockstream.cpp
- sockstream.h
-
Make sure that you include all of your dependencies including BCI2000Remote.h
#include "BCI2000Remote.h" #include <string> #include <vector> #include <iostream>
-
Next, instantiate the BCI2000Remote object
int main( int argc, char* argv[] ) { // Instantiate a BCI2000Remote object BCI2000Remote bci; // Assume that Operator executable resides in the same directory as this program. std::string path = ( argc > 0 ) ? argv[0] : ""; size_t pos = path.find_last_of( "\\/" ); path = ( pos != std::string::npos ) ? path.substr( 0, pos + 1 ) : ""; // Start the Operator module, and connect bci.OperatorPath( path + "Operator" ); if( !bci.Connect() ) { std::cerr << bci.Result(); return -1; }
-
Specify your startup modules and any flags you wish to start them with
// Startup modules const char* modules[] = { "SignalGenerator --LogMouse=1", "ARSignalProcessing", "CursorTask" }; std::vector<std::string> vModules( &modules[0], &modules[0] + sizeof( modules ) / sizeof( *modules ) ); if( !bci.StartupModules( vModules ) ) { std::cerr << bci.Result(); return -1; }
-
Load any parameter files and set subject information
bci.LoadParametersRemote( "../parms/examples/CursorTask_SignalGenerator.prm" ); bci.SubjectID( "SUB" ); // Start a run if( !bci.Start() ) { std::cerr << bci.Result(); return -1; }
-
And here we just have BCI2000 send us back a feedback signal:
std::string state; while( bci.GetSystemState( state ) && state == "Running" ) { double value = 0; bci.GetControlSignal( 1, 1, value ); std::cout << "Control signal: " << value << ", press Enter to proceed" << std::flush; std::string line; std::getline( std::cin, line ); } return 0; }
- Now that you've added those components to your code all you have to do is compile and run it. You should see the BCI2000 operator along with the system log, source watcher, and the timing window. Note: Be sure to compile this in your prog folder. This can be done by navigating again to Configuration Properties -> General, and changing the Output Directory to the path of the BCI2000 prog folder. File:BCIRemote 7.jpg
#include "BCI2000Remote.h" #include <string> #include <vector> #include <iostream> int main( int argc, char* argv[] ) { // Instantiate a BCI2000Remote object BCI2000Remote bci; // Assume that Operator executable resides in the same directory as this program. std::string path = ( argc > 0 ) ? argv[0] : ""; size_t pos = path.find_last_of( "\\/" ); path = ( pos != std::string::npos ) ? path.substr( 0, pos + 1 ) : ""; // Start the Operator module, and connect bci.OperatorPath( path + "Operator" ); if( !bci.Connect() ) { std::cerr << bci.Result(); return -1; } // Startup modules const char* modules[] = { "SignalGenerator --LogMouse=1", "ARSignalProcessing", "CursorTask" }; std::vector<std::string> vModules( &modules[0], &modules[0] + sizeof( modules ) / sizeof( *modules ) ); if( !bci.StartupModules( vModules ) ) { std::cerr << bci.Result(); return -1; } // Load a parameter file, and set subject information bci.LoadParametersRemote( "../parms/examples/CursorTask_SignalGenerator.prm" ); bci.SubjectID( "SUB" ); // Start a run if( !bci.Start() ) { std::cerr << bci.Result(); return -1; } // Print feedback signal std::string state; while( bci.GetSystemState( state ) && state == "Running" ) { double value = 0; bci.GetControlSignal( 1, 1, value ); std::cout << "Control signal: " << value << ", press Enter to proceed" << std::flush; std::string line; std::getline( std::cin, line ); } return 0; }
MatlabTutorial
- Open the Matlab file you would like to integrate with BCI2000.
- Locate the BCI2000RemoteLib file in the BCI2000 prog folder, and BCI2000RemoteLib.h in src\core\Operator\BCI2000Remote, and load these into the Matlab file. Modify the file paths in loadlibrary(...) as needed.
Note that BCI2000RemoteLib's file name has a "64" appended when it was built in 64 bit mode, and a "32" if built iin 32 bit mode.
In this tutorial, we will be using the 64 bit version.
%% c library load, initial part clc;clear; if not(libisloaded('bci')) loadlibrary('C:\BCI2000.x64\prog\BCI2000RemoteLib64','C:\BCI2000.x64\src\core\Operator\BCI2000Remote\BCI2000RemoteLib.h', 'alias', 'bci') end libfunctions('bci')
- Locate Operator.exe in the prog folder. Recover the memory, change the directory, and make the window visible in BCI2000. Again, modify the file path if it differs.
%need to call BCI2000Remote_Delete to recover the memory bciHandle = calllib('bci', 'BCI2000Remote_New'); calllib('bci', 'BCI2000Remote_SetOperatorPath', bciHandle,'C:/BCI2000.x64/prog/Operator'); if calllib('bci', 'BCI2000Remote_Connect', bciHandle) ~= 1 fprintf('Could not connect to BCI2000, aborting.') calllib('bci', 'BCI2000Remote_Delete', bciHandle); return end calllib('bci', 'BCI2000Remote_Execute', bciHandle,'Change directory $BCI2000LAUNCHDIR',0); calllib('bci', 'BCI2000Remote_SetWindowVisible', bciHandle,1);
- Add in the proper modules (Signal Generator, Signal Processor, and Application). Also, load in BCI2000 parameters, add/set state variables, and include any desired functionality. Refer to the documentation Technical_Reference:BCI2000Remote_Library for more information on BCI2000 remote functions. Also refer to the example Matlab code under Programming_Reference:BCI2000Remote_Class#Matlab_Example.
Video
Also see the PsychoPy page for more details on the installation process, APIs, and hooks.