User Tutorial:BCI2000Remote

From BCI2000 Wiki
Revision as of 16:56, 11 September 2023 by Mellinger (talk | contribs) (→‎MatlabTutorial)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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

  1. 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)
    
  2. 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:
    BCIRemote1.png
    Here is how you instantiate the operator object:
    BCIRemote 2.png
    Here is how to customize the operator and connect to it:
    BCI2000Remote2.png
    Here is how you add your states:
    BCIRemote 4.png
    How to specify your signal source, signal processing, and application modules:
    BCIRemote 5.png
    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:
    BCIRemote 6.png

  3. And finally, how to tell BCI2000 to set states to a value during runtime:
    BCIRemote 6.png
    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. BCIRemote 7.png

C++ Tutorial

  1. 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.
  2. 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.
  3. 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.
  4. 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

  5. Make sure that you include all of your dependencies including BCI2000Remote.h
    #include "BCI2000Remote.h"
    #include <string>
    #include <vector>
    #include <iostream>
    
  6. 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;
      }
    
  7. 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;
      }
    
  8. 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;
      }
    
  9. 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;
    }
    
  10. 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

  1. Open the Matlab file you would like to integrate with BCI2000.
  2. 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')
    
  3. 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);
    
  4. 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.

See also