Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions neo/rawio/bci2000rawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,16 @@ def _parse_header(self):
gain = param_defs["SourceChGain"]["value"][chan_ix]

if isinstance(gain, str):
r = re.findall(r"(\d+)(\D+)", gain)
# some files have strange units attached to gain
# in that case it is ignored
if len(r) == 1:
gain = r[0][0]
gain = float(gain)
gain = float(re.match(_number_pattern, gain).group(1))

offset = param_defs["SourceChOffset"]["value"][chan_ix]
if isinstance(offset, str):
offset = float(offset)
offset = float(re.match(_number_pattern, offset).group(1))
# BCI2000 defines uV = (raw - SourceChOffset) * SourceChGain
# while neo computes raw * gain + offset
offset = -offset * gain

sig_channels.append((ch_name, chan_id, sr, dtype, units, gain, offset, stream_id, buffer_id))
self.header["signal_channels"] = np.array(sig_channels, dtype=_signal_channel_dtype)
Expand Down Expand Up @@ -254,6 +254,10 @@ def _event_arrays_list(self):
return self._my_events


# a decimal number such as "0.003" or "-1.5e-3" followed by optional units such as "muV"
_number_pattern = r"([-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?)(\w*)"


def parse_bci2000_header(filename):
# typically we want parameter values in Hz, seconds, or microvolts.
scales_dict = {
Expand All @@ -280,7 +284,8 @@ def rescale_value(param_val, data_type):
if param_val.lower().startswith("0x"):
param_val = int(param_val, 16)
elif data_type in ["int", "float"]:
matches = re.match(r"(-*\d+)(\w*)", param_val)
pattern = r"(-*\d+)(\w*)" if data_type == "int" else _number_pattern
matches = re.match(pattern, param_val)
if matches is not None: # Can be None for % in def, min, max vals
param_val, unit_str = matches.group(1), matches.group(2)
param_val = int(param_val) if data_type == "int" else float(param_val)
Expand Down Expand Up @@ -325,7 +330,7 @@ def parse_dimensions(param_list):
if temp != "[ State Vector Definition ]":
raise NeoReadWriteError(f"State definitions not found in header {filename}")
state_defs = []
state_def_dtype = [("name", "a64"), ("length", int), ("startVal", int), ("bytePos", int), ("bitPos", int)]
state_def_dtype = [("name", "S64"), ("length", int), ("startVal", int), ("bytePos", int), ("bitPos", int)]
while True:
temp = fid.readline().decode("utf8").strip()
if len(temp) == 0 or temp[0] == "[":
Expand Down
21 changes: 19 additions & 2 deletions neo/test/rawiotest/test_bci2000rawio.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import unittest

import numpy as np

from neo.rawio.bci2000rawio import BCI2000RawIO
from neo.test.rawiotest.common_rawio_test import BaseTestRawIO

Expand All @@ -14,12 +16,27 @@ class TestBCI2000RawIO(
):
rawioclass = BCI2000RawIO

entities_to_download = ["bci2000/eeg1_1.dat", "bci2000/eeg1_2.dat", "bci2000/eeg1_3.dat"]

entities_to_download = [
"bci2000",
]

entities_to_test = ["bci2000/eeg1_1.dat", "bci2000/eeg1_2.dat", "bci2000/eeg1_3.dat"]

def test_scaling(self):
# The header has SourceChGain= 64 0.01617 0.01591 ... and SourceChOffset= 64 43 55 ...
# BCI2000 defines uV = (raw - SourceChOffset) * SourceChGain
reader = BCI2000RawIO(filename=self.get_local_path("bci2000/eeg1_1.dat"))
reader.parse_header()
signal_channels = reader.header["signal_channels"]
np.testing.assert_allclose(signal_channels["gain"][:2], [0.01617, 0.01591], rtol=1e-6)

raw = reader.get_analogsignal_chunk(0, 0, 0, 3, 0, channel_indexes=[0, 1])
uV = reader.rescale_signal_raw_to_float(raw, dtype="float64", stream_index=0, channel_indexes=[0, 1])
expected = (raw.astype("float64") - [43, 55]) * [0.01617, 0.01591]
np.testing.assert_allclose(uV, expected, rtol=1e-5)
# first samples as decoded by BCI2000's own FileReader.py (BCI2kReader)
np.testing.assert_allclose(uV[:, 0], [-16.218512, 1.3744501, -9.23307], rtol=1e-5)


if __name__ == "__main__":
unittest.main()