204 lines
7.4 KiB
Python
204 lines
7.4 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
||
|
|
"""
|
||
|
|
Convert BDF file to MAT format.
|
||
|
|
|
||
|
|
This script converts a BDF (Biosemi Data Format) EEG file to .mat format,
|
||
|
|
matching the structure of eeg_data.mat.
|
||
|
|
|
||
|
|
Structure of eeg_data.mat:
|
||
|
|
- data: (n_samples, n_channels) float64
|
||
|
|
- chn: (1, n_channels) object - channel names
|
||
|
|
- sample_rate: (1, 1) int64
|
||
|
|
- node_number: (1, 1) int64
|
||
|
|
- t: (n_samples, 1) float64 - time vector in seconds
|
||
|
|
- electrode_name: (1, n_channels) object - electrode names (10-20 system)
|
||
|
|
- electrode_xyz: (n_channels, 3) float64 - electrode 3D coordinates
|
||
|
|
- electrode_coord_system: (1,) <U21
|
||
|
|
- meta: (1, 1) structured - metadata
|
||
|
|
"""
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
import scipy.io
|
||
|
|
import mne
|
||
|
|
from datetime import datetime
|
||
|
|
|
||
|
|
|
||
|
|
def get_standard_electrode_coords():
|
||
|
|
"""
|
||
|
|
Standard 10-20 system electrode coordinates.
|
||
|
|
Returns a dictionary mapping electrode names to x, y, z coordinates.
|
||
|
|
Coordinates are approximate spherical projections.
|
||
|
|
"""
|
||
|
|
coords = {
|
||
|
|
'FP1': (-0.0293, 0.0903, -0.0033),
|
||
|
|
'FP2': (0.0293, 0.0903, -0.0033),
|
||
|
|
'FPZ': (0.0, 0.0903, -0.0033),
|
||
|
|
'AF7': (-0.0658, 0.0734, -0.0224),
|
||
|
|
'AF3': (-0.0350, 0.0812, -0.0183),
|
||
|
|
'AF4': (0.0350, 0.0812, -0.0183),
|
||
|
|
'AF8': (0.0658, 0.0734, -0.0224),
|
||
|
|
'F7': (-0.0815, 0.0467, -0.0336),
|
||
|
|
'F5': (-0.0667, 0.0503, -0.0351),
|
||
|
|
'F3': (-0.0489, 0.0560, -0.0370),
|
||
|
|
'F1': (-0.0254, 0.0584, -0.0384),
|
||
|
|
'FZ': (0.0, 0.0584, -0.0384),
|
||
|
|
'F2': (0.0254, 0.0584, -0.0384),
|
||
|
|
'F4': (0.0489, 0.0560, -0.0370),
|
||
|
|
'F6': (0.0667, 0.0503, -0.0351),
|
||
|
|
'F8': (0.0815, 0.0467, -0.0336),
|
||
|
|
'FT7': (-0.0880, 0.0229, -0.0397),
|
||
|
|
'FC5': (-0.0699, 0.0317, -0.0402),
|
||
|
|
'FC3': (-0.0514, 0.0362, -0.0411),
|
||
|
|
'FC1': (-0.0268, 0.0383, -0.0419),
|
||
|
|
'FCZ': (0.0, 0.0383, -0.0419),
|
||
|
|
'FC2': (0.0268, 0.0383, -0.0419),
|
||
|
|
'FC4': (0.0514, 0.0362, -0.0411),
|
||
|
|
'FC6': (0.0699, 0.0317, -0.0402),
|
||
|
|
'FT8': (0.0880, 0.0229, -0.0397),
|
||
|
|
'T7': (-0.0958, 0.0, -0.0411),
|
||
|
|
'T8': (0.0958, 0.0, -0.0411),
|
||
|
|
'C5': (-0.0739, 0.0, -0.0425),
|
||
|
|
'C3': (-0.0544, 0.0, -0.0436),
|
||
|
|
'C1': (-0.0283, 0.0, -0.0444),
|
||
|
|
'CZ': (0.0, 0.0, -0.0444),
|
||
|
|
'C2': (0.0283, 0.0, -0.0444),
|
||
|
|
'C4': (0.0544, 0.0, -0.0436),
|
||
|
|
'C6': (0.0739, 0.0, -0.0425),
|
||
|
|
'TP7': (-0.0880, -0.0229, -0.0397),
|
||
|
|
'CP5': (-0.0699, -0.0317, -0.0402),
|
||
|
|
'CP3': (-0.0514, -0.0362, -0.0411),
|
||
|
|
'CP1': (-0.0268, -0.0383, -0.0419),
|
||
|
|
'CPZ': (0.0, -0.0383, -0.0419),
|
||
|
|
'CP2': (0.0268, -0.0383, -0.0419),
|
||
|
|
'CP4': (0.0514, -0.0362, -0.0411),
|
||
|
|
'CP6': (0.0699, -0.0317, -0.0402),
|
||
|
|
'TP8': (0.0880, -0.0229, -0.0397),
|
||
|
|
'P7': (-0.0815, -0.0467, -0.0336),
|
||
|
|
'P5': (-0.0667, -0.0503, -0.0351),
|
||
|
|
'P3': (-0.0489, -0.0560, -0.0370),
|
||
|
|
'P1': (-0.0254, -0.0584, -0.0384),
|
||
|
|
'PZ': (0.0, -0.0584, -0.0384),
|
||
|
|
'P2': (0.0254, -0.0584, -0.0384),
|
||
|
|
'P4': (0.0489, -0.0560, -0.0370),
|
||
|
|
'P6': (0.0667, -0.0503, -0.0351),
|
||
|
|
'P8': (0.0815, -0.0467, -0.0336),
|
||
|
|
'PO7': (-0.0658, -0.0734, -0.0224),
|
||
|
|
'PO5': (-0.0503, -0.0744, -0.0258),
|
||
|
|
'PO3': (-0.0350, -0.0812, -0.0183),
|
||
|
|
'POZ': (0.0, -0.0829, -0.0172),
|
||
|
|
'PO4': (0.0350, -0.0812, -0.0183),
|
||
|
|
'PO6': (0.0503, -0.0744, -0.0258),
|
||
|
|
'PO8': (0.0658, -0.0734, -0.0224),
|
||
|
|
'O1': (-0.0293, -0.0903, -0.0033),
|
||
|
|
'OZ': (0.0, -0.0903, -0.0033),
|
||
|
|
'O2': (0.0293, -0.0903, -0.0033),
|
||
|
|
'CB1': (-0.0618, -0.0380, -0.0387),
|
||
|
|
'CB2': (0.0618, -0.0380, -0.0387),
|
||
|
|
'A1': (-0.0958, 0.0, 0.0),
|
||
|
|
'A2': (0.0958, 0.0, 0.0),
|
||
|
|
}
|
||
|
|
return coords
|
||
|
|
|
||
|
|
|
||
|
|
def bdf_to_mat(bdf_path, output_path, subject_id='unknown', session_id='unknown'):
|
||
|
|
"""
|
||
|
|
Convert BDF file to MAT format matching eeg_data.mat structure.
|
||
|
|
|
||
|
|
Parameters
|
||
|
|
----------
|
||
|
|
bdf_path : str
|
||
|
|
Path to the input BDF file.
|
||
|
|
output_path : str
|
||
|
|
Path to the output MAT file.
|
||
|
|
subject_id : str, optional
|
||
|
|
Subject identifier. Default is 'unknown'.
|
||
|
|
session_id : str, optional
|
||
|
|
Session identifier. Default is 'unknown'.
|
||
|
|
"""
|
||
|
|
print(f'Loading BDF file: {bdf_path}')
|
||
|
|
raw = mne.io.read_raw_bdf(bdf_path, preload=True, verbose=False)
|
||
|
|
|
||
|
|
# Get basic info
|
||
|
|
ch_names = raw.ch_names
|
||
|
|
n_channels = len(ch_names)
|
||
|
|
sfreq = int(raw.info['sfreq'])
|
||
|
|
data = raw.get_data()
|
||
|
|
|
||
|
|
# BDF data shape: (n_channels, n_samples)
|
||
|
|
# Convert to eeg_data.mat format: (n_samples, n_channels)
|
||
|
|
data = data.T
|
||
|
|
n_samples = data.shape[0]
|
||
|
|
|
||
|
|
# Create time vector (in seconds)
|
||
|
|
t = np.arange(n_samples) / sfreq
|
||
|
|
t = t.reshape(-1, 1)
|
||
|
|
|
||
|
|
# Create channel names array (matching eeg_data.mat structure)
|
||
|
|
chn = np.array([[name] for name in ch_names], dtype=object)
|
||
|
|
|
||
|
|
# Create electrode names (same as channel names for BDF)
|
||
|
|
electrode_name = np.array([[name] for name in ch_names], dtype=object)
|
||
|
|
|
||
|
|
# Get electrode coordinates
|
||
|
|
standard_coords = get_standard_electrode_coords()
|
||
|
|
electrode_xyz = np.zeros((n_channels, 3))
|
||
|
|
for i, name in enumerate(ch_names):
|
||
|
|
if name in standard_coords:
|
||
|
|
electrode_xyz[i] = standard_coords[name]
|
||
|
|
else:
|
||
|
|
print(f'Warning: No standard coordinate for electrode {name}')
|
||
|
|
|
||
|
|
# Create metadata structure
|
||
|
|
start_time_str = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||
|
|
meta = np.array([(subject_id, session_id, 'CMS/DRL', start_time_str)],
|
||
|
|
dtype=[('subject_id', 'O'), ('session_id', 'O'),
|
||
|
|
('ref', 'O'), ('start_time', 'O')])
|
||
|
|
|
||
|
|
# Create the EEG structure (matching eeg_data.mat format)
|
||
|
|
eeg_struct = np.array([(data, chn, [[sfreq]], [[n_channels]],
|
||
|
|
t, electrode_name, electrode_xyz,
|
||
|
|
'buzsaki', meta)],
|
||
|
|
dtype=[('data', 'O'), ('chn', 'O'),
|
||
|
|
('sample_rate', 'O'), ('node_number', 'O'),
|
||
|
|
('t', 'O'), ('electrode_name', 'O'),
|
||
|
|
('electrode_xyz', 'O'), ('electrode_coord_system', 'O'),
|
||
|
|
('meta', 'O')])
|
||
|
|
|
||
|
|
# Save to MAT file
|
||
|
|
print(f'Saving to: {output_path}')
|
||
|
|
scipy.io.savemat(output_path, {'eeg': eeg_struct}, do_compression=True)
|
||
|
|
|
||
|
|
print(f'\nConversion complete!')
|
||
|
|
print(f' Channels: {n_channels}')
|
||
|
|
print(f' Samples: {n_samples}')
|
||
|
|
print(f' Duration: {n_samples / sfreq:.2f} seconds')
|
||
|
|
print(f' Sample rate: {sfreq} Hz')
|
||
|
|
print(f' Data shape: {data.shape}')
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
# File paths
|
||
|
|
bdf_path = r'D:\Ivey\Code_New_Proj\Debug_Depression\algorithm_version_0521_v0\0515-18.bdf'
|
||
|
|
output_path = r'D:\Ivey\Code_New_Proj\Debug_Depression\algorithm_version_0521_v0\0515-18.mat'
|
||
|
|
|
||
|
|
# Convert
|
||
|
|
bdf_to_mat(bdf_path, output_path, subject_id='lvpeng', session_id='01')
|
||
|
|
|
||
|
|
# Verify the output
|
||
|
|
print('\n=== Verification ===')
|
||
|
|
mat_data = scipy.io.loadmat(output_path)
|
||
|
|
eeg = mat_data['eeg'][0, 0]
|
||
|
|
|
||
|
|
print(f'Output file keys: {list(mat_data.keys())}')
|
||
|
|
print(f'eeg.data shape: {eeg["data"].shape}')
|
||
|
|
print(f'eeg.chn shape: {eeg["chn"].shape}')
|
||
|
|
print(f'eeg.sample_rate: {eeg["sample_rate"][0, 0]}')
|
||
|
|
print(f'eeg.t shape: {eeg["t"].shape}')
|
||
|
|
print(f'eeg.electrode_name: {eeg["electrode_name"].shape}')
|
||
|
|
print(f'eeg.electrode_xyz shape: {eeg["electrode_xyz"].shape}')
|
||
|
|
print(f'eeg.electrode_coord_system: {eeg["electrode_coord_system"][0]}')
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
main()
|