Data Format Description

Units of Measure

For EEG, EMG, etc BrainFlow returns uV.

For timestamps BrainFlow uses UNIX timestamp, this count starts at the Unix Epoch on January 1st, 1970 at UTC. Precision is microsecond, but for some boards timestamps are generated on PC side as soon as package was received.

You can compare BrainFlow’s timestamp with time returned by code like this:

import time
print (time.time ())

Generic Format Description

Methods like:

get_board_data ()
get_current_board_data (max_num_packages)

Return 2d double array [num_channels x num_data_points], rows of this array represent different channels like EEG channels, EMG channels, Accel channels, Timesteps and so on, while columns in this array represent actual packages from a board.

Exact format for this array is board specific. To keep the API uniform, we have methods like:

# these methods return an array of rows in this 2d array containing eeg\emg\ecg\accel data
get_eeg_channels (board_id)
get_emg_channels (board_id)
get_ecg_channels (board_id)
get_accel_channels (board_id)
# and so on, check docs for full list
# also we have methods to get sampling rate from board id, get number of timestamp channel and others
get_sampling_rate (board_id)
get_timestamp_channel (board_id)
# and so on

For some boards like OpenBCI Cyton, OpenBCI Ganglion, etc we cannot separate EMG, EEG, EDA and ECG and in this case we return exactly the same array for all these methods but for some devices EMG and EEG channels will differ.

If board has no such data these methods throw an exception with UNSUPPORTED_BOARD_ERROR exit code.

Using the methods above, you can write completely board agnostic code and switch boards using a single parameter! Even if you have only one board using these methods you can easily switch to Synthetic Board or Streaming Board.

OpenBCI Specific Data

Special Channels for OpenBCI Cyton Based Boards

Cyton-based boards from OpenBCI support different output formats, described here.

For Cyton based boards, we add Cyton End byte to a first channel from:

get_other_channels (board_id)

If Cyton End byte is equal to 0xC0 we add accel data. To get rows which contain accel data use:

get_accel_channels (board_id)

If Cyton End byte is equal to 0xC1 we add analog data. To get rows which contain analog data use:

get_analog_channels (board_id)

For analog data, we return int32 values. But since from low level API, we return double array, these values are converted to double without any changes.

Also we add raw unprocessed bytes to the second and next channels returned by:

get_other_channels (board_id)

If Cyton End Byte is outside this range, we drop the entire package.

Check this example for details:

import argparse
import time
import numpy as np

import brainflow
from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds
from brainflow.data_filter import DataFilter, FilterTypes


def main():
    parser = argparse.ArgumentParser()
    # use docs to check which parameters are required for specific board, e.g. for Cyton - set serial port
    parser.add_argument('--ip-port', type=int, help='ip port', required=False, default=0)
    parser.add_argument('--ip-protocol', type=int, help='ip protocol, check IpProtocolType enum', required=False,
                        default=0)
    parser.add_argument('--ip-address', type=str, help='ip address', required=False, default='')
    parser.add_argument('--serial-port', type=str, help='serial port', required=False, default='')
    parser.add_argument('--mac-address', type=str, help='mac address', required=False, default='')
    parser.add_argument('--other-info', type=str, help='other info', required=False, default='')
    parser.add_argument('--streamer-params', type=str, help='other info', required=False, default='')
    parser.add_argument('--board-id', type=int, help='board id, check docs to get a list of supported boards',
                        required=True)
    parser.add_argument('--log', action='store_true')
    args = parser.parse_args()

    params = BrainFlowInputParams()
    params.ip_port = args.ip_port
    params.serial_port = args.serial_port
    params.mac_address = args.mac_address
    params.other_info = args.other_info
    params.ip_address = args.ip_address
    params.ip_protocol = args.ip_protocol

    if (args.log):
        BoardShim.enable_dev_board_logger()
    else:
        BoardShim.disable_board_logger()

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

    board.start_stream()
    time.sleep(5)
    board.config_board('/2')  # enable analog mode only for Cyton Based Boards!
    time.sleep(5)
    data = board.get_board_data()
    board.stop_stream()
    board.release_session()

    """
    data[BoardShim.get_other_channels(args.board_id)[0]] contains cyton end byte
    data[BoardShim.get_other_channels(args.board_id)[1....]] contains unprocessed bytes
    if end byte is 0xC0 there are accel data in data[BoardShim.get_accel_channels(args.board_id)[....]] else there are zeros
    if end byte is 0xC1 there are analog data in data[BoardShim.get_analog_channels(args.board_id)[....]] else there are zeros
    """
    print(data[BoardShim.get_other_channels(args.board_id)[0]][0:5])  # should be standard end byte 0xC0
    print(data[BoardShim.get_other_channels(args.board_id)[0]][-5:])  # should be analog and byte 0xC1

    DataFilter.write_file(data, 'cyton_data.csv', 'w')


if __name__ == "__main__":
    main()