Introduction

I thought about buying an EEG device for a long time but had a lot of doubts due to the high price. One of the reasons why the neurotech domain was interesting for me is because it’s still quite small, so it’s simple for newcomers to make an impact and build something notable. Also, as of right now, this field is mostly occupied by researchers and universities and has a lack of software engineers. Now I think such a shortage of experienced engineers and big companies involved is the main obstacle for the entire industry. But on the other hand, this field has great potential and growing rapidly.

In June 2018 I bought my first EEG recording device, it was Cyton Board from OpenBCI. At that moment there were much fewer neurotech companies focusing on low-cost equipment, so I was choosing between Muse, Emotiv, and OpenBCI. I ordered Cyton, because it provides access to raw data without extra pay. There were no plans to implement BrainFlow or any other similar project, in fact, I didn’t have anything concrete in my mind, just wanted to learn something new and get an interesting experience.

It was pretty straightforward to run OpenBCI GUI and take a look at EEG data in real-time, but as a software engineer, I wanted to have programmatic access to a device and develop my own apps. Soon, I realized that SDKs provided by OpenBCI were not good. So, I started to work on BrainFlow project, there was no grant or company behind this project and still not. I will enumerate the most common problems with SDKs provided by companies since it should help you to understand decisions made in BrainFlow. But before we move on with the problems and challenges let’s answer the question of why it’s important to develop good SDKs and provide tech support.

Why developer tools and community are important?

Many neurotech companies sell hardware and provide SDK for free, so they don’t earn on SDKs and it can be challenging for them to allocate enough resources to assist developers, especially for startups.

In my opinion, in the short term, the outcome of such investments is not visible for companies but in the long term, it’s enormous. It decreases time to market for new hardware, also good support for developers and robust and clean SDK simplifies application development for engineers outside your company, they build apps for the users and users of such apps buy the hardware. And it’s a loop.

Investments into developer tools and community are especially important because in software development it’s very hard to predict what end-users will like or not, but if you simplify the process of application development for engineers you increase the chance that killer app will occur.

Common problems with SDKs and proposed solution

First of all, I want to mention that most of these problems are not specific to a particular company or particular device, they are quite common since such SDK development is not simple and requires relevant experience which is not so common among engineers.

No device abstraction

Some companies take care of device abstraction on the SDK level and it’s great. Such SDKs are usually written by relatively big companies and are much easier to use. But they do it only for their own boards. If users want to switch vendors or target devices from another company they will need to implement such abstraction by themselves or rewrite the entire application because quite often SDKs from different companies are not compatible even in terms of programming languages and OSes supported. Also, many companies do not design abstraction even for their own devices. It doesn’t allow to reuse of the code and doesn’t allow to implement features like synthetic boards, emulators, etc. As we will see later it also doesn’t allow to implement bindings for different programming languages.

How it’s implemented in BrainFlow:

board_id = BoardIds.SYNTHETIC_BOARD
exg_channels = BoardShim.get_exg_channels(board_id)
params = BrainFlowInputParams()
# set params for your device if needed
# .....


board = BoardShim(board_id, params)
board.prepare_session()

board.start_stream()
time.sleep(10)
data = board.get_board_data()
board.stop_stream()
board.release_session()

exg_data = data[exg_channels, :]
print(exg_data)

Support for different programming languages

There are different applications for biosensors, such devices can be integrated into games, can be used in apps to measure your mental states, in neuromarketing apps, can be used by researchers, and so on. For all these cases we need different programming languages, for integration with game engines we may need C++(Unreal Engine, CryEngine and many more) or C#(Unity), researchers prefer Python, Julia, Matlab or R, for general purpose or mobile apps we may need Java(OpenBCI GUI), C#(Bonsai), C++(Neuromore Studio, MNE Scan). To match all of these cases as developers we need to add support for the vast majority of programming languages. There are only two possible solutions for that:

  • Implement a protocol for streaming instead of SDK or even application for data streaming. The disadvantage of this approach is a topic for another article, we will not focus on it here.
  • Implement data acquisition and parsing in low-level code(C/C++ or something like Rust) and reuse this code in other languages. It’s how BrainFlow works.

This idea intersects a lot with device abstraction, any changes in the interface between low-level code(C/C++) and high-level code(Python, Java, etc) lead to a significant amount of work. Strict device abstraction on the C/C++ level makes this interface stable and helps a lot.

I don’t know any other SDK to work with biosensors that does it and supports so many programming languages.

Manual Testing

In BrainFlow we currently have 7 different programming languages, support 20+ devices and 4 OSes. Also, native code should be compiled for each OS and CPU architecture and not x-platform from the box. To build and test it fully we need to run at least 7 * 20 * 4 = 560 tests on different machines. Also, it makes sense to run tests for code style check, static analyzer, memory leak tests, tests for different versions of programming languages(Python3 and Python2, Java 8 and Java 11, etc), and many more. Without CI/CD pipelines that run device emulators in the cloud, we could not do it and could not deliver reliable code.

Also, if there are only manual tests, it prevents new features to be implemented because the cost of change gets incredible if you need to test so many things manually.

More information about emulators and pipelines used in BrainFlow.

Bad Code

Almost every big project has some bugs which should be fixed and it’s doable and ok as long as the overall design is correct and if there are tests to catch them. But issues mentioned above make this process almost impossible.

Copy-paste from one of the SDKs:

def read(n):
    bb = self.ser.read(n)
    if not bb:
        self.warn('Device appears to be stalled. Quitting...')
        sys.exit()
        raise Exception('Device Stalled')
        sys.exit()
        return '\xFF'
    else:
        return bb

To prevent something like that in BrainFlow we are trying to force code review policy and add tests wherever possible. More info at our dev page.

Most notable events in BrainFlow history

You can check the most notable events in BrainFlow history using this link.