The Plugin Interface

The Plugin Interface

The plugin interface opens custom access to internal raw and preprocessed data during real-time processing, including information of which channels are selected. This allows to perform additional calculations within the plugin and/or to export data for external processing, e.g., in Matlab. Future versions will also include access to statistical GLM data, including protocol and design matrix, as well as classifier data.

The source code of (at present) one plugin is included with this release, allowing to quickly learn about the provided API functions. The “TSIPluginInterface.h” file lists all available commands for which a detailed description will be available soon. The abbreviation “TSI” as well as the command prefix “t” refer to Turbo-Satori.

A plugin needs to be coded in C++. Available templates and the example plugin source code allow for simple creation of own plugins, requiring only to fill-in one (or more) “execute” functions, and to customize a few C functions used to interrogate plugin information, such as its display name. The main task of the plugin coder consists in adding code to the “executeTimePoint()” function that is called at each time point during real-time processing.

The next topic describes how to use implemented plugins from TSI; then it is described when and how TSI calls a selected plugin before, during and after real-time processing; this information is helpful to learn how to write the called plugin functions.

Using Plugins

When one clicks the Plugins menu, a list of all available plugins will be shown. To fill the Plugins menu, the program looks into the “Plugins_32” (32-bit TSI on 32 or 64-bit operating system) or “Plugins_64” (64-bit TSI on 64-bit operating system) sub folder within the “TSIExtensions” folder. If one wants to install new compiled plugins, simply copy the respective library file (extension “.dll” on Windows, “.so” on Linux, “.dylib” on Mac) in that folder. Note that the full version of the program supporting real-time interaction with the NIRx recording device only runs on Windows 32, because only this operating system is supported by NIRx company at present.

The “TSIExtensions” folder is located within the “Documents” folder. On Windows XP, the Documents folder is typically accessible via the “My Documents” directory in Explorer, or through the full path “C:and SettingsDocuments” (these names may vary depending on the used language and the version of the Windows operating system). On Windows Vista and Windows 7, the “Documents” folder is located in “C:”. On Mac OS X and Linux, the “Documents” folder is located within your “Home” folder, i.e., “/Users/USER/Documents” on Mac OS X and “/ home/USER/Documents” on Linux. Note that the directory structure in newer Windows operating systems (Vista/7) is similar to the Linux and Mac OS X folder layout.

To start a plugin, simply click its name in the Plugins menu. Note that one needs to start a plugin prior to real-time processing, since the Plugins menu will not be accessible during incremental processing. Turbo-Satori then opens and shows the Plugin window containing a Log text field, that is used by the plugin, to inform the user about ongoing processing. The messages appearing in the Log fully depend on the plugin code, i.e. they are unique to the executed plugin. The plugin may print some information about its operation to the Log and it can also ask for information, such as choices for subsequent real-time processing, and it will then waiting until it is called repeatedly during real-time processing.

TSI Plugin Interactions

When the Plugins menu is opened, Turbo-Satori is interrogating all plugin files (shared libraries with the extension “.dll” on Windows, “.so” on Linux and “.dylib” on Mac OS X) located in the “Plugins_32/64” folder. In each detected plugin library file, TSI calls a few simple C functions that must be defined in the plugin in order to get basic information about the plugin, such as its name, a short description of its function, the plugin author, and (optionally) a help file. The returned plugin name is used to fill the Plugins menu.

When a specific plugin is selected in the Plugins menu, TSI will create a plugin object by instantiating the defined plugin class; more specifically, the “createPlugin()” C function is called and serves as a factory to create the plugin object; a pointer to that object is returned to TSI and stored, so that it can be used subsequently to call predefined methods, that must be implemented by the plugin class. In order to enforce a set of known class methods, a plugin class must be derived from the generic “TSIPluginInterface” class, defining the interface using virtual functions.

The plugin object is normally destroyed by TSI when the user closes the Plugin dialog, typically after real-time processing. The plugin object is also destroyed when the initialization function “initPlugin()” returns “false”, e.g. in case that some prerequisites are not met. If “initPlugin()” returns “true”, the Plugin dialog is launched, which is used to send information from the plugin to the user. Then TSI calls the “executePreRun()” function that can be used for setting up the plugin or to interrogate information from the user. After the function has returned, the plugin “sleeps” until it is called later. After real-time processing has been started, the “executeTimePoint()” function will be called once at each time point, allowing execution of code for custom data processing/export. Note that the plugin programmer is responsible for keeping processing time within these functions short, since TSI will wait until the plugin returns, i.e. until these functions have finished its computations at a given step. After incremental processing is finished, TSI calls the “executePostRun()” function of the active plugin that can be used to do final processing. When the Plugin dialog is closed, the destructor of the plugin object is called and can be used for final clean-up, such as releasing any allocated memory. In summary, the following functions are called in a typical real-time session:

When selecting a plugin in the Plugins menu:

  1. TSI calls C-function createPlugin() of the plugin that calls itself “new ” to create plugin object, executes constructor, and returns object pointer (called “pluginObject” below).

  2. TSI calls member function “pluginObject->initPlugin()”, allowing plugin to check prerequisites, including successful access of TSI plugin API functions, and to check whether the TSI version is appropriate.

  3. TSI calls member function “pluginObject->executePreRun()” allowing plugin to perform setup operations (e.g. allocating memory for later use), and to present dialogs to the user to make specific choices, or to enter relevant information (e.g. where to store data).

  4. At each time step after starting real-time processing:

  5. TSI calls member function “pluginObject->executeTimePoint()”. This function allows the access to the raw channel wavelength data (wavelength 1 and 2), as well as preprocessed data. Using raw data, custom code can be used, for example, to add additional preprocessing operations. Access is also available. When the user selects oxy/deoxy conversion in the GUI, the accessed preprocessed data will have been converted to oxy/deoxy concentration, and any filter (at present only moving average low pass filter) will have been applied, if selected in GUI. Note that the time point value retrieved by the “tGetCurentTimePoint()” API function is 1-based. Since the data values are accessed 0- based, the retrieved frame (time point) value needs to be decremented by 1 before accessing the current time point. Note that a plugin has access not only to the current data of each channel, but also to all previous values.

  6. After the last frame has been processed:

  7. TSI calls member function “pluginObject->executePostRun()” allowing to do some post-run processing and to inform user about completion of the plugin.

  8. When user closes the Plugin dialog:

  9. TSI deletes the plugin object executing the “delete pluginObject” statement. This results in a call of the plugin destructor that can be used to clean-up any allocated memory.


Writing Plugins

Essential Plugin Files

A plugin needs to derive its plugin definition from the “TSIPluginInterface” class. The “TSIPluginInterface.h” file must, thus, be added to any TSI plugin project containing your custom .cpp file(s). Since it is intended to not change the currently provided access functions, but only to add new functions, updating the “TSIPluginInterface.h” include files does not require changing any of the files containing your own code, i.e. it will be possible to overwrite the file with a new version, and to recompile the plugin code. Updating the “TSIPluginInterface.h” file will only be necessary, if you want to use additional functionality of Turbo-Satori made available with new releases. The latest “TSIPluginInterface.h” include file is provided with the source code of the example plugins.

Following the class definition, the “TSIPluginInterface.h” file contains definition of access functions with parameter types that can be used in the plugin class. The snapshot below shows the beginning of the TSI API. The “tLogText” command, for example, is defined with a “void” return type and one input parameter that is a pointer to a c string.

//
// These are typedef's for dynamic access of TSI (Turbo-Satori) functions - version with "static" definitions
// Commands start with "t" to indicate that these are Turbo-Satori access functions

// general functions
typedef void (*GetVersionOfTSIPrototype)(int *, int *, int *);
static GetVersionOfTSIPrototype tGetVersionOfTSI;

typedef void (*GetTSIPathPrototype)(char *);
static GetTSIPathPrototype tGetTSIPath;

typedef void (*GetPluginsPathPrototype)(char *);
static GetPluginsPathPrototype tGetPluginsPath;


// output in log window
typedef void (*LogTextPrototype)(const char *);
static LogTextPrototype tLogText;

// dialogs to get/provide information - note these fns are only usable in non-incremental calls (ignored in "execute()" plugin fn)
typedef void (*MessageBoxPrototype)(const char *);
static MessageBoxPrototype tMessageBox;

typedef int (*YesNoMessageBoxPrototype)(const char *);
static YesNoMessageBoxPrototype tYesNoMessageBox;
...

In the plugin code, it will be possible to write commands like “tLogText(”Hello from Plugin“)”, and the provided text will be visible to the user in the Log text field of the Plugin dialog. The available commands will be described in more detail in subsequent topics. Following the definition of the signature of the TSI API functions, a “InitTSIAccess()” function (not shown) is defined that retrieves the corresponding function pointers from TSI at run time. Besides the “TSIPluginInterface.h” file, a new plugin needs as a minimum only one C++ and one header file, defining the custom plugin class, e.g. a “MyPlugin.cpp” file and a “MyPlugin.h” file. The provided plugin examples contain an additional “global.h” header file that is only used to determine the platform on which the plugin is compiled in order to include platform-specific standard C/C++ header files. Depending on the needs of your plugin, you may, of course, add additional C/C++ files that support your programming goals, e.g. files from “Boost” or “Numerical Recipes”.

As described, the custom class includes the “TSIPluginInterface.h” header file allowing to derive the custom class “ExamplePlugin” from the abstract “TSIPluginInterface” class. Within the class declaration, all methods that were defined “virtual” in the abstract class, are now non-virtual and need to be defined in the implementation file “ExamplePlugin.cpp”. Note that for a new plugin, one only needs to change the class name. The only special part in this example are the definitions of two protected variables “stepCounter” and “nChannels”, that are used in the plugin to store information across different calls from TSI. You would remove these variables for your own plugin and add other variables as needed. The second part of the plugin header file contains declarations of the described C functions that need to be defined in the implementation plugin file; these functions will be called from TSI to retrieve basic information about the plugin. Note that this part needs not to be changed.

Compiling Plugins

It should be possible to write a plugin with your favorite compiler and Integrated Development Environment (IDE). On Windows, the Microsoft Visual C++ compilers in Visual Studio 2008/2010/2012 compilers have been tested and can be used to create plugins. On Mac OS X, you can use XCode or QtCreator, and on Linux you can use QtCreator or KDevelop. On Mac and Linux, the provided Brain Innovation plugins have been compiled directly from the console using the GNU gcc compiler or with Qt Creator. While Qt Creator is a cross-platform IDE from Digia (that got it from Nokia which acquired Trolltech, the original creator of the Qt library) to develop C++ software using the Qt library, it can be used also to develop any (non-GUI) C/C++ program/library. Project files (Visual Studio project “.vcproj” file for Windows, Makefiles for Mac and Linux) are provided and can be adapted for use with own plugins.

General Access Functions

void tGetVersionOfTSI(int major, int minor, int *subminor)

The “tGetVersionOfTSI” function returns the version of the running Turbo-Satori in the variables “Major”, “Minor” and “SubMinor”. Usually used in the “initPlugin()” function. The returned version values can be used to check whether the used version of TSI is recent enough to ensure that all TSI API functions, used in the plugin, are available. For this test, you can use the “checkVersionGreaterEqualVersion()” function, for details check the source code of the provided example plugins.

void tGetTSIPath(char *cTSIPath)

Use this function to retrieve the path to the folder containing the Turbo-Satori executable. On Windows, for example, this will typically result in a C string like "C:/Program Files/Turbo- Satori“. Note that slashes are returned on all platforms in Unix convention (right slashes) and that the path does not end with a slash (except when a root directory is returned). void tGetPluginsPath(char *cPluginsPath) This function retrieves the path to the folder containing Turbo-Satori plugins. Use this function to find the location of your plugin and any subfolders (i.e. directories containing help files or resource files). On Windows, for example, this function will typically result in a C string like “C:/Documents and Settings//My Documents/TSIExtensions/Plugins”. Note that slashes are returned on all platforms in Unix convention (right slashes) and that the path does not end with a slash (except when a root directory is returned).

GUI Functions

void tLogText(const char *text)

This function sends a line of text to the Log text field in the Plugin window of Turbo-Satori. The text is provided as a C string. This function is the only GUI function during real-time processing and should be used to provide information about (successful) processing in the plugin; it is also useful to help debugging plugin code.

void tMessageBox(const char *message)

This function displays a message box with text provided in the “message” C string. This function cannot be used during real-time processing; the appropriate place to use it is the “executePreRun()” function.

int tGetIntegerInput(const char *instruction, int default_value, int min_val, int max_val)

Presents a dialog with the provided “instruction” text and an edit field, in which the user can enter an integer value in the range “min_val” to “max_val”. The edit field shows the provided “default_value” initially. When the user clicks the “Ok” button of the dialog, the entered value is returned. If the special value “min_val - 1” is returned, the user has clicked the “Cancel” button or pressed the “Escape” key. This function cannot be used during real-time processing.

float tGetFloatInput(const char *instruction, float default_value, float min_val, float max_val)

Presents a dialog with the provided “instruction” text and an edit field, in which the user can enter a float (real) value in the range “min_val” to “max_val”. The edit field initially shows the provided “default_value”. When the user clicks the “Ok” button of the dialog, the entered value is returned. If the special value “min_val - 1” is returned, the user has clicked the “Cancel” button or pressed the “Escape” key. This function cannot be used during real-time processing.

int tGetTextInput(const char instruction, const char default_text, char *return_text)

Presents a dialog with the provided “instruction” text and an edit field, in which the user can enter a string, i.e. a file name. The edit field initially shows the provided “default_text” text. When the user clicks the “OK” button of the dialog, the entered text is returned in the “return_text” parameter. Note that you must provide enough space for the C string “return_text” (see example code for more information). The return value signals success (value “1”) or failure (“0”). This function cannot be used during real-time processing.

int tYesNoMessageBox(const char *cquestion)
Presents a dialog with the provided text “question” and with a “Yes” and a “No” button. The function returns value “1” if the user clicks “Yes”, and value “0” otherwise. This function cannot be used during real-time processing.

int tGetOpenFileName(const char instruction, const char default_folder_file, const char type_of_files, char return_filename)

Presents a standard “File Open” dialog with the provided “instruction” text. The contents of the folder provided in the “default_folder” parameter will initially be shown in the dialogs file browser. The “type_of_files” parameter can be used to filter the files in a folder with respect to a provided file type. You must use the syntax “ (.)" to specify a filter, i.e. "Protocol Files (.prt)”. When the user clicks the “Open” button of the dialog, the selected file name is returned in the “return_filename” parameter. Note that you must provide enough space for the C string “return_filename” (see example code for more information). The return value signals success (value “1”) or failure (“0”), the latter indicating that the user has clicked the “Cancel” button. This function cannot be used during real-time processing.

int tGetSaveFileName(const char instruction, const char default_folder_file, const char type_of_files, char return_filename)

Presents a standard “File Save” dialog with the provided “instruction” text. The contents of the folder provided in the “default_folder_file” parameter will be shown in the dialogs file browser initially. If not only a path but also a filename is provided, the filename will be used as the default name for the to-be-saved file. The “type_of_files” parameter can be used to filter the files in a folder with respect to a provided file type. You must use the syntax “ (.)" to specify a filter, i.e. "Protocol Files (.prt)”. When the user clicks the “Save” button of the dialog, the selected file name is returned in the “return_filename” parameter. Note that you must provide enough space for the C string “return_filename” (see example code for more information). The return value signals success (value “1”) or failure (“0”), the latter indicating that the user has clicked the “Cancel” button. This function can not be used during real-time processing.

Basic Project Info

float tGetSamplingRate()

Provides the sampling rate of the current project.

int tGetCurrentTimePoint()

Provides the number of the currently processed frame (time point) during real-time processing as an integer. Note that the returned value is 1-based in the executeTimePoint() function, i.e. when the first step is processed the function returns “1” not “0”; this is important when the return value is used to access time-related information; in this case subtract “1” from the returned value.

int tGetNrOfChannels()

Provides the number of channels available in the current setup of the NIRx device. Use this value as the upper bound when looping through all channels.

void tGetValuesFeedbackFolder(char *cValuesFeedbackFolder)

Provides the path of the feedback folder for calculated neurofeedback values as a C string; note that the provided pointer must point to a pre-allocated array that is large enough for the returned path (a buffer of 513 bytes is recommended). The values feedback folder can be used to store the result of custom calculations. It is located under the folder containing the protocol specified prior to starting real-time processing and is usually named “NeurofeedbackValues”.

void tGetImagesFeedbackFolder(char *cImagesFeedbackFolder)

Provides the path of the feedback folder for snapshots of the thermometer display as a C string; note that the provided pointer must point to a pre-allocated array that is large enough for the returned path (a buffer of 513 bytes is recommended). The images feedback folder is located under the folder containing the protocol specified prior to starting real-time processing and is usually named “NeurofeedbackImages”.

Selected Channels Info

int tGetNrOfSelectedChannels() Provides the number of channels that are currently selected in the GUI. When processing selected channels (e.g. to average their signals), this function must be called at each time point since it can change anytime. Inspect the provided ExamplePlugin code for more details.

int * tGetSelectedChannels()

Provides a pointer to an array containing the indices of the currently selected channels. Use the number returned by the “tGetNrOfSelectedChannels” as an upper bound when looping through this array. This is important since the size of the referenced array changes dynamically depending on the number of channels that are currently selected in the GUI. Inspect the provided ExamplePlugin code for more details.

Get Raw Data

float tGetRawDataScaleFactor()

Provides the scale factor set in the GUI to multiply the raw data wavelength values. While not necessary, It is recommended that accessed data (see below) is multiplied by this value to be compatible with the values displayed in the GUI. Inspect the provided ExamplePlugin code for more details.

float tGetRawDataWL1(int ch, int frame)

Provides the value of the raw data for wavelength 1 for the specified channel “ch” and the specified time point “frame”. If the current values are of interest, “frame” should be set to one less the value obtained by the “tGetCurrentTimePoint()” function. To be compatible with the values displayed in the GUI, it is recommended to multiply the retrieved value by the scale value obtained from the “tGetRawDataScaleFactor()” function.

float tGetRawDataWL2(int ch, int frame)

Provides the value of the raw data for wavelength 2 for the specified channel “ch” and the specified time point “frame”. If the current values are of interest, “frame” should be set to one less the value obtained by the “tGetCurrentTimePoint()” function. To be compatible with the values displayed in the GUI, it is recommended to multiply the retrieved value by the scale value obtained from the “tGetRawDataScaleFactor()” function.

Get Preprocessed Data

int tIsDataOxyDeoxyConverted()

Returns integer “1” if oxygenated/deoxygeneated values are requested in the GUI (default) and if concentration values can be calculated, i.e. after the specified baseline period has passed.

float tGetOxyDataScaleFactor()

Provides the scale factor set in the GUI to multiply the preprocessed oxy/deoxy data values. While not necessary, It is recommended that accessed oxy/deoxy concentration data (see below) is multiplied by this value to be compatible with the values displayed in the GUI. Inspect the provided ExamplePlugin code for more details.

float tGetDataOxy(int ch, int frame)

Provides the value of the oxygenated data for the specified channel “ch” and the specified time point “frame”. If the current values are of interest, “frame” should be set to one less the value obtained by the “tGetCurrentTimePoint()” function. To be compatible with the values displayed in the GUI, it is recommended to multiply the retrieved value by the scale value obtained from the “tGetOxyDataScaleFactor()” function.

float tGetDataDeoxy(int ch, int frame)

Provides the value of the de oxygenated data for the specified channel “ch” and the specified time point “frame”. If the current values are of interest, “frame” should be set to one less the value obtained by the “tGetCurrentTimePoint()” function. To be compatible with the values displayed in the GUI, it is recommended to multiply the retrieved value by the scale value obtained from the “tGetOxyDataScaleFactor()” function.

Protocol, DM, GLM functions

int tGetFullNrOfPredictors()

Provides the number of predictors of the design matrix.

float tGetValueOfDesignMatrix(int pred, int timepoint, bool Oxy); ()
Provides the value of a predictor at a given time point of the current design matrix. Note that the design matrix always contains the “full” set of predictors, a reduced set of predictors is only used internally (predictors that are not used internally are those containing only “0.0” entries up to the current time point). Note that the “timepoint” parameter must be smaller than the value returned by the “tGetCurrentTimePoint()” function. For details, see the provided example plugins.

float tGetBetaOfChannel(int ch, int beta, bool Oxy)

Provides the signal value as a 4-byte float value of the channel specified by the parameter “ch” for the given time point (0-based indices). The given “timepoint” parameter must be smaller than the value obtained by the “tGetCurrentTimePoint()” function.

float tGettValueOfChannel(int ch, bool Oxy, int *contrast)
Provides the signal value as a 4-byte float value of the channel specified by the parameter “ch” for the given time point (0-based indices). The given “timepoint” parameter must be smaller than the value obtained by the “tGetCurrentTimePoint()” function.

int tGetProtocolCondition(int frame)
Provides the index of the currently “active” condition of the protocol (0-based), i.e. the condition that has a defined interval enclosing the defined time point.

Copyright © Brain Innovation B.V. 2019. All rights reserved.