I’m trying to simulate EEG signals using MNE library. I used this code and I’m able to generate EEG data for different channels. But I have a few doubts about this method.
from mne import create_info
from mne.io import RawArray
from numpy.random import default_rng
def create_toy_data(n_channels=35, duration=25, sfreq=250, seed=None):
rng = default_rng(seed)
data = rng.standard_normal(size=(n_channels, duration * sfreq)) * 5e-6
ch_names = [f'EEG {i+1:03d}' for i in range(n_channels)] # Create channel names like 'EEG 001', 'EEG 002', etc.
info = create_info(ch_names, sfreq, "eeg")
return RawArray(data, info)
# Create the toy data
raw = create_toy_data()
# Plot the data with channel names visible
raw.plot(show=True, title='EEG Signal with Channel Names', show_options=True, n_channels=128)
Though this generates the output I need, I want to know if this is accurate? As the documentation suggest other ways like forward model. Some of the questions I have are:
Oh alright. I’ll give that method another try and see if I can generate EEG signals. My issue in that method was that I can’t change the number of electrodes. If you have any idea on that, pls let me know.
But thanks for the help
The forward model defines the signal transformation from a fix number of sensors to a fix number of sources, taking into account the head geometry. You should be able to define the number of EEG electrodes through this. I haven’t tried myself, but it’s likely something along those lines:
Define an info with mne.create_info which contains your EEG channels
Set a montage which match your EEG channels positions with info.set_montage()
Define a source space within your head geometry
Compute the forward model which goes from your sensor to your sources
Thanks for this method. Using your method, I was able to get some output but there’s no signal. I’m trying to find where the mistake is, still no luck though.
I’ll add the code here with the output. Let me know if you can spot the mistake.
import numpy as np
import mne
from mne.simulation import simulate_raw
# Step 1: Define an info object with specific EEG channels
# Use the standard 10-20 montage channel names
montage = mne.channels.make_standard_montage('standard_1020')
ch_names = montage.ch_names[:32] # Use the first 32 channels from the montage
sfreq = 250 # Sampling frequency in Hz
ch_types = ['eeg'] * len(ch_names)
# Create the info object
info = mne.create_info(ch_names=ch_names, sfreq=sfreq, ch_types=ch_types)
# Step 2: Set the montage to the info object
info.set_montage(montage)
# Step 3: Define a source space within the head geometry
# For simplicity, we'll use the 'sample' subject data from MNE
data_path = mne.datasets.sample.data_path()
subjects_dir = data_path / 'subjects'
subject = 'sample'
# Create source space
src = mne.setup_source_space(subject, spacing='oct6', subjects_dir=subjects_dir, add_dist=False)
# Step 4: Compute the forward model
conductivity = (0.3, 0.006, 0.3) # Conductivities for the head layers
model = mne.make_bem_model(subject=subject, ico=4, conductivity=conductivity, subjects_dir=subjects_dir)
bem = mne.make_bem_solution(model)
# Compute the forward solution (lead field matrix)
fwd = mne.make_forward_solution(info, trans='fsaverage', src=src, bem=bem, eeg=True, meg=False)
# Load or create a label for a specific cortical region
label_name = 'cuneus-lh' # Example label name
labels = mne.read_labels_from_annot(subject, parc='aparc', subjects_dir=subjects_dir)
label = next((l for l in labels if label_name in l.name), None)
if label is None:
raise RuntimeError(f"No label found with name '{label_name}'")
# Step 5: Use simulation functions to generate the simulated EEG data
# Set the sampling frequency to match the info object
sfreq = info['sfreq'] # Get the sampling frequency from the info object
# Define an event and create a SourceSimulator
times = np.arange(0, 2, 1.0 / sfreq) # Simulate 2 seconds of data
# Define source activation function (simple oscillation)
def data_fun(times):
return np.sin(10 * 2 * np.pi * times) # 10 Hz sine wave
# Create dummy events (start time 0, id 1, duration 1)
events = np.array([[0, 0, 1]])
# Create a SourceSimulator
source_simulator = mne.simulation.SourceSimulator(src, tstep=1/sfreq)
# Add simulated data to the SourceSimulator
source_simulator.add_data(label, data_fun(times), events)
# Simulate raw data
raw_sim = simulate_raw(info, source_simulator, forward=fwd, verbose=True)
raw_sim.set_eeg_reference(projection=True)
# Step 6: Plot the simulated data
raw_sim.plot(title='Simulated EEG data')
Your raw signals are way too big in amplitude for the viewer at the moment. That is why you see these vertical lines as well. You can check the signal amplitudes in raw_sim and rescale your sinusoid approximately using that information. Note the red bar in the left upper corner of the picture indicating the scale of 40 microvolts.
Since the forward operation is linear you could change your data_fun to return A*np.sin(…) and the new output will also be scaled by A. MNE expects the unit of EEG signals to be V so you have to rescale quite a lot.
This is only for this value, if I adjust the zero, it goes back to something similar to before. I’m actually confused on what’s happening. It’s nothing compared to an EEG signal.
Well, the output now makes a lot more sense! Remember that the forward model only consists of gains. The EEG is modeled as a linear combination of the source activations. (Approximately : EEG = Gainmatrix * source activations)
Your source activation is currently a sinusoidal wave, which is what you see back on the simulated EEG. You can make the source activity more complex by adding noise or multiple frequencies as well as adding multiple sources.