The BCI device adapters module provides utilities to work with data from different brain-computer interface acquisition systems. The module handles electrode mapping, resampling, channel grouping, and calibration to create a standardized data processing pipeline.
Different BCI systems use different:
- Electrode layouts (10-20 system, custom arrays, etc.)
- Channel naming (numeric indices, anatomical names, custom labels)
- Sampling rates (250 Hz, 1000 Hz, 30 kHz, etc.)
- Calibration parameters (gain, offset, impedance)
The adapters module provides a consistent interface for working with all these systems.
from bci_compression.adapters import map_channels
# Define mapping from device channels to standard electrode names
mapping = {
'ch_0': 'Fp1',
'ch_1': 'Fp2',
'ch_2': 'C3',
'ch_3': 'C4'
}
# Apply mapping (data is channels x samples)
standardized_data = map_channels(raw_data, mapping)from bci_compression.adapters import resample
# Downsample from 30kHz to 10kHz
resampled = resample(data, src_rate=30000, dst_rate=10000, method='polyphase')
# Upsample from 250Hz to 1000Hz
resampled = resample(data, src_rate=250, dst_rate=1000, method='fft')from bci_compression.adapters import apply_channel_groups
# Define channel groups (e.g., by brain region)
groups = {
'motor_strip': [8, 9, 10, 11],
'visual_cortex': [20, 21, 22, 23],
'emg_pair': [30, 31]
}
# Average channels within each group
grouped = apply_channel_groups(data, groups, reducer='mean')
# Access grouped data
motor_data = grouped['motor_strip'] # Shape: (1 x samples)from bci_compression.adapters import apply_calibration
# Define calibration parameters
calibration = {
'scale': [1.0, 1.2, 0.95, 1.1], # Per-channel gain
'offset': [0.0, -0.5, 0.3, 0.0], # Per-channel DC offset
'bad_channels': [2] # Channels to zero out
}
# Apply calibration
calibrated = apply_calibration(data, calibration)The OpenBCI adapter provides pre-configured mappings for OpenBCI devices.
from bci_compression.adapters.openbci import OpenBCIAdapter
# Create adapter for Cyton 8-channel
adapter = OpenBCIAdapter(device='cyton_8ch')
# Convert raw data to standardized format
standard_data = adapter.convert(raw_data)
# Resample to target rate
resampled = adapter.resample_to(standard_data, target_rate=1000)
# Get channel groups
groups = adapter.get_channel_groups()
# Returns: {'frontal': [0,1], 'central': [2,3], ...}# Create adapter for Daisy 16-channel
adapter = OpenBCIAdapter(device='daisy_16ch')
# Convert and resample in one step
from bci_compression.adapters.openbci import convert_openbci_to_standard
standard_data = convert_openbci_to_standard(
raw_data,
device='daisy_16ch',
target_rate=1000
)from bci_compression.adapters.openbci import OpenBCIAdapter
from bci_compression.adapters import apply_calibration
from bci_compression.algorithms.lossless import NeuralLZ77Compressor
# 1. Create adapter
adapter = OpenBCIAdapter(device='cyton_8ch')
# 2. Convert to standard format
standard_data = adapter.convert(raw_data)
# 3. Resample if needed
resampled_data = adapter.resample_to(standard_data, target_rate=1000)
# 4. Apply calibration
calibration = {
'scale': [1.0] * 8,
'offset': [0.0] * 8,
'bad_channels': []
}
calibrated_data = apply_calibration(resampled_data, calibration)
# 5. Compress
compressor = NeuralLZ77Compressor()
compressed = compressor.compress(calibrated_data)
print(f"Compression ratio: {calibrated_data.nbytes / len(compressed):.2f}x")Mappings can be saved and loaded from YAML or JSON files:
device: openbci_cyton_8ch
sampling_rate: 250
channels: 8
mapping:
ch_0: Fp1
ch_1: Fp2
ch_2: C3
ch_3: C4
ch_4: P7
ch_5: P8
ch_6: O1
ch_7: O2
channel_groups:
frontal: [0, 1]
central: [2, 3]
parietal: [4, 5]
occipital: [6, 7]from bci_compression.adapters import load_mapping_file, save_mapping_file
from bci_compression.adapters.openbci import OpenBCIAdapter
# Save a mapping
adapter = OpenBCIAdapter(device='cyton_8ch')
adapter.save_mapping('my_device_mapping.yaml')
# Load and use a mapping
adapter = OpenBCIAdapter.from_file('my_device_mapping.yaml')Map channels from one naming/indexing convention to another.
Parameters:
data: Input neural data (channels x samples)mapping: Dictionary mapping source to target channelsinput_format: 'index' or 'name'output_format: 'index' or 'name'
Returns: Remapped neural data
Resample neural data with anti-aliasing filter.
Parameters:
data: Input neural datasrc_rate: Source sampling rate in Hzdst_rate: Destination sampling rate in Hzmethod: 'fft' or 'polyphase'axis: Axis along which to resample
Returns: Resampled neural data
Apply logical channel grouping for spatial filtering.
Parameters:
data: Input neural data (channels x samples)groups: Dictionary mapping group names to channel indicesreducer: 'mean', 'median', 'first', or 'concat'
Returns: Dictionary mapping group names to processed data
Apply per-channel calibration (scaling, offsets, bad channel masking).
Parameters:
data: Input neural data (channels x samples)calibration: Dictionary with 'scale', 'offset', 'bad_channels' keyschannels: Optional list of channel indices to calibrate
Returns: Calibrated neural data
Adapter for OpenBCI device data.
Parameters:
device: 'cyton_8ch' or 'daisy_16ch'custom_mapping: Optional custom mapping dictionary
Methods:
convert(data, apply_mapping=True): Convert to standardized formatresample_to(data, target_rate, method='polyphase'): Resample dataget_channel_groups(): Get channel groupingssave_mapping(filepath): Save mapping to filefrom_file(filepath): Load adapter from mapping file
- Always specify data format: Make it clear whether your data is channels x samples or samples x channels
- Use mapping files: Store device configurations in version-controlled YAML files
- Validate after resampling: Check that signal characteristics are preserved
- Document calibration parameters: Keep records of gain/offset values for reproducibility
- Group related channels: Use channel grouping to reduce dimensionality before compression
from bci_compression.adapters.openbci import OpenBCIAdapter
# Define your device mapping
MY_DEVICE_MAPPING = {
'device': 'my_custom_array',
'sampling_rate': 2000,
'channels': 32,
'mapping': {
'ch_0': 'electrode_A1',
'ch_1': 'electrode_A2',
# ... more mappings
},
'channel_groups': {
'region_1': [0, 1, 2, 3],
'region_2': [4, 5, 6, 7],
}
}
# Create adapter
adapter = OpenBCIAdapter(custom_mapping=MY_DEVICE_MAPPING)- Polyphase resampling is faster than FFT for integer rate ratios
- FFT resampling provides higher quality for arbitrary rate ratios
- Channel grouping can significantly reduce data size before compression
- Bad channel masking should be done before compression to avoid wasting bits