Using PyBCI

This chapter should give you an idea how to use PyBCI. Actually, there are two possible ways to use it: Either you’ll write a Python Script and let in run, like it is done in usage.py in the examples folder, or you may start a console like Ipython and type in the steps one by one. In any case, the main functions to call are same. These are explained in the following.

Note

The main steps explained here are also shown in usage.py in the example folder - just have a look at the code and let it run! It is even possible to type in the commands you’ll find there bit by bit into a Python console (f.i., using IPython).

Setting up the BCI

Creating a configuration file

In principle, the only thing you need to set up the BCI, is a configuration file. It is recommended the implemented generator by:

from PyBCI.tools.ConfigHelper import make_config

Like this, you can generate the configuration file by calling:

make_config('our_config_file.cfg', sample_rate, numof_channels, mode,
           server = 'localhost', security_mode = False,
           saving_mode = False, data_file = 'Nofile', format = 'binary',
           resolution = 0.1, returning_speed = 8, channels = [1...numof_channels]
           color_bg = 'grey', color_trigger = 'black',
           size_window = (1000, 800))

Besides of the name of your configuration file, three parameter are obligatory: The sample rate, the number of channels you want to get data from (which is not necessarily the number of channels you are getting the data from) and the signing mode, which declares if you want to have the possibility to give signs in a separate window (‘signs_enabled’) or not (‘signs_disabled’). If the mode is ‘signs_enabled_xy’, the signs are shown with the specified color (default is black) in a window with the declared size (in pixels width, height, default is 1000, 800) on a specified background (default color is white). While PyBCI is running, you can trigger these signs, declaring the desired shape, size, showing time and eventually text or texture (see below).

The various modes that enable signing are: ‘signs_enabled’: OpenGL C++ signing mode, this is the only signing mode including the possibility to show bitmap signs yet. ‘signs_enabled_py’: OpenGL Python signing mode ‘signs_enabled_tk’: Tkinter signing mode - this is the only signing mode including the possibility to show text signs yet, it may be pretty slow and still partly unusuable though.

Otherwise declare ‘signs_disabled’.

Note

Up to now, there is a time delay for the return of the signing fucntion when showing signs using OpenGL Python mode. Thus, C++ mode is currently the ‘main’ mode.

The parameter channels is a list that declares the channel labels you want to get data from. The channel label is the number # in the Brain Recorder Software, thus the vertical position in the Brain Recoder list. By default, the channels from 1 to numof_channels are read. Please note that the order of the channels in the data array you’ll get when collecting data is the one the channels are listed in channels. You are able to change these labels while PyBCI is running.

Specify the resolution of your EEG signal (declared in the Brain Recorder, usually either 0.1 or 10), if you need a data conversion to microvolt.

By setting the returning_speed, you can set the returning speed of data arrays. If you are dependent on receiving the data as fast as possible, you should choose a high level. Thanks to the improved CPU architecture the speed is set on a pretty fast value (8) by default anyway, so increasing the speed is usually not necessary. The levels that are possible range from -9 (very slow) up to 9 (very fast). For ‘hardship cases’, possible exceptions are implemented with -10 as the slowest level that is possible and 10 (as fast as possible). It is also possible to change this parameter when PyBCI is running.

Usually this software will be running on the same computer, that is receiving Brain Recorder Data via TCP/IP-Port. If this is the case, you may skip the server argument, otherwise it has to be specified with the name of the server that is getting the data.

The other optional parameters are either part of the functions to save data or of the security mode, that is explained in the parameter section.

It is possible to write your configuration file manually. The only thing you have to do is to write a txt file that looks like this

[visualization]
mode = signs_enabled
color_bg = white
color_trigger = black
window_size = 1000, 800

[security]
security_mode = False

[data]
saving_mode = True
file = example_file.dat
format = binary

[technics]
numof_channels = 10
channels = 1,2,3,4,5,6,7,8,9,10
sample_rate = 500
resolution = 0.1
speed = 8
server = localhost

Please note that the labels that are used here for the variables as well as the sections [ ] are obligatory. Like when creating the configuration file automatically, the only obligatory parameters that you have to specify in any case, are numof_channels, sample_rate (both section [technics]) and mode (section [visualization]).

There is also a template configuration file, named BCI_config_templ.cfg that you can adapt to your needs, with its documentation in BCI_config_templ_doc.txt.

Saving Data

If you want to save the data in a file while getting it, you have to switch on the saving mode in your configuration file. In this case, you’ll have to specify a data_file with the desired format. If this mode is switch on, the data is written in the file each time you call the get_data function.

Another option is to let this mode switch off, open a file manually and - after the reading procedure to save valuable time - save the data calling:

Our_BCI.save_data(data_file, format, data)

The data is saved transposed, so that each column represents one channel. Possible data formats are ‘plain’ (ascii txt), ‘pickle’, ‘binary’ or ‘mat’ (MATLAB-file).

Note

There is also a binary file implemented in the C++ source code to save all incoming data from Brain Recorder in one file. This is some kind of security measure to be sure to have all data in any case lying somewhere. This file is called ‘data.bin’ (so avoid naming your own data file like this).

Note

Additionally, there is a file ‘stop_remarks.txt’ implemented in the C++ source code. In this file, the system time and the number of returned arrays are written. This is for the case that you have stopped the Recorder unvoluntarily and try to find out the matching sample in your data file.

Starting the BCI

After having declared the BCI class by:

from PyBCI import BCI

whatever you want to do, the first thing to do is starting the BCI, calling the main BCI class with your configuration file:

Our_BCI = BCI(our_config_file)

Calling the BCI class starts two new threads, one, that uses an implemented C++ function to create (and hold) the connection to the TCP/IP-Port, the other to get the possibility to give signs while getting the data. For the documentation of the implemented C++ functions the its chapter.

Changing Parameters

It may happen that something is not preset in the way you want. For example, you may get speed problems (overflow, overstrained CPU...), because data is available faster than you are able to get it returned. To avoid that, simply call:

Our_BCI.set_returning_speed(level)

This function resets the returning speed of data arrays.

A pretty helpful thing is the security mode. If it is switched on, a warning is raised if the number of returned blocks is not equal to the received ones. That may be useful if you want to be sure not to miss samples/data blocks or to avoid reading blocks twice. Usually you will not want to start the BCI and get the very first data yet. Because of this, the security mode is switched off by default (otherwise you would get a warning message). The best way to use it is to start the BCI first and start security mode not until you want to get the data, either manually, calling:

Our_BCI.set_security_mode(True)

or as an argument when calling a function to get data, with the latter as the usual way.

Another thing you may want to change are the channel labels.

Note

Be careful: The channel labels you specify here have to match with the number # (and not the label or the physical channel number) that is declared in the Brain Recorder Software.

By default the labels are [1, 2, 3 ... numof_channels]. To change that, you have to change each channel separately, calling:

Our_BCI.change_channellabels(channel, label)

with the channel as the position in channels and the matching label (as # in the Recorder software).

An alternative for relabeling channels is just to put the channels you want to ‘read’ at the beginning of the Recorder Software one below the other.

Getting the data

There are two possible functions to finally get the data: To get just the current data block, call:

datablock = Our_BCI.get_datablock()

The size of the datablock depends on the returning speed and on the sample rate. After having started the BCI, this number is stored in:

Our_BCI.numof_samples

Probably the ‘main’ function to get data is to call:

data = Our_BCI.get_data(time[, security_mode = True, supervision_mode = True])

For the specified time (in seconds) data is stored and then returned. Additionally, the data is stored in the class attribute:

Our_BCI.data

If the security mode is switched on (this is default), a warning is raised if the number of returned blocks is not equal to the read ones.

By default, the supervision mode is switched on. This is pretty usuable if it may happen that anything goes wrong during getting the data and you have to stop the Brain Recorder. If this is the case, the stopping is identified and the data collection is restarted for the time than has been specified previously. If saving_mode is switched on, five zeros are written into the data file to ‘sign’ this stopping.

Both the datablock function and the data function return the data in a two-dimensional numpy array [channels][samples]. Please note that the order of the channels in the data array you’ll get is the one the channels are listed in channels. You are able to change these labels while PyBCI is running.

Note

IMPORTANT: If the Brain Recorder is stopped while the data is being collected and the whole process is restarted, the data is not returned anymore (actually, it is, but ‘too early’ and therefore as a NoneType). Then, the only way to get the data is by the class attribute data mentioned above.

External data access

It is possible to access the data from other systems than the one the EEG Recording Software is running. All you have to do is to change the according part of the Configuration File. If you declare, for instance, the IP adress of the Recording machine as the server, you’ll get all data this one receives on the system PyBCI is running.

Note

This should even work with more than one system connecting the Recording server via PyBCI - up to now, this is not tested yet.

Correcting artefacts

Using PyBCI, it is also possible to estimate the impact of certain artefacts - usually eye movements and blinks - and to remove this impact from the data. Usually, you’ll have to do some kind of calibration session for this estimation. In the example folder, you can find eog_correction.py, that shows exactly this procedure. Additionally, a short explanation is given by the following.

You can get the module you need by:

from PyBCI.tools.EyemoveCorrector import *

Assuming, you have already started the BCI like shown above, you’ll probably need four conditions like:

conditions = ['resting', 'horizontal', 'vertical', 'blinks']

with ‘resting’ as some kind of baseline, horizontal and vertical eye movements and blinks as the different condition.

Note

Of course you may choose to evaluate more or less than four conditions - this is just an example.

For each condition you have to collect enough data for a valid mean value and then average over the trials and samples within one condition to get one ‘representive’ value for each channel in each condition. The detailed required structure of these channel values you can find in the example file and in the documentation of the module.

Having collected the data for these (four) conditions, you’ll have to call:

impact = estimate_impact(condition_means[0], condition_means[1:4])

with condition_means[0] as the baseline and condition_means[1:numof_conditions+1] as the ‘artefacted’ conditions.

With this function, an impact array of eye movement artefacts is estimated. The difference between the baseline and the artefact conditions is used for calculating the impact of the latter condition on all channels of the baseline condition. It is assumed that the difference for one channel is composed of the influence of all the other conditions and channels (and herewith also of the EOG channels).

Note

It is necessary for a valid estimation that the channels used for collecting data for the different conditions are both equal and in the same order.

You’ll get an impact array, that you will need for the following removement of the artefacts. The only thing you need to do for this removal is to call:

corrected_data = remove_impact(impact, data)

with impact as the returned impact array and data with the collected data, f.i. by calling get_data(). As a result, you’ll get returned a signal array without any activity, that had previously caused the difference between the two conditions used for calculating the impact array. In other words, every activity is removed from the data, that correlates with the difference of the baseline and artefact conditions.

Note

It is again necessary for a valid estimation that the channels of the data array are both equal and in the same order as in the data arrays used for estimating the impact array.

Giving Signs

A useful thing is to have the possibility to give a sign, for example before collecting data. Therefore, you have to choose one of the modes that enable giving signs in your configuration file. A new window (colors and size is also specified in the config file) is opened and a sign with the shape you have to specify is shown each time you call:

Our_BCI.trigger_sign(shape, size, time, [texture = R', text='NoText'])

A trigger of the size in the range between 0 (not at all) and 1 (whole window) is shown for time milliseconds and you can choose for shape between

1 or ‘triangle’ for a triangular shape - 2 or ‘square’ for a quadratic shape, - 3 or ‘text’ for text that you can specify using the parameter text - and 4 or ‘bmp’ for bitmaps that you can specify using the parameter texture. ‘Triangle’ is the default if the shape you specify is invalid.

Note

Up to now, BMP mode is possible just available when using the OpenGL C++ signing mode, and only for the textures ‘R’ (default), ‘L’, ‘r’ and ‘l’ on a grey background. These bitmaps are not that perfectly drawn, but instead are exactly of the same area.

Text mode is enabled just in the Tkinter mode.

In any mode, when the sign is shown a trigger (‘5’) is additionally sent via the parallel port.

Ending the BCI

If you choose to end the BCI, you should definitely call:

Our_BCI.end_BCI()

The data files and the server connection are closed and allocated memory is deleted. Possible memory problems are avoided herewith.