MNE version: 1.7.1
operating system: Windows 10
Hi, all
I’m using mne for time-frequency analysis of ERP data. I have some question. I have extracted the tfr-power of theta band. I want to use a curve to see how the time frequency data changes. But in the end of the power, I see a strangely smooth ascent. GPT tells me this is from edge effect. I want to know how to deal with it. Thanks.
I usually extend the original epochs by ±0.5 s (i.e., in your example I’d define them from -0.7 s to 2.5 s), compute the TFR, and then crop 0.5 s from each end of the result.
This is just an add-on for users who can’t extend the desired epoch length (e.g. pre-stimulus window, etc.). I wrote a function a while ago that mirrors or zero-pads epochs on both ends, which might be helpful. I also ran simulations and wrote a test for this function, feel free to reach out.
Run your time-frequency algorithm on the mirrored/zero-padded data before cropping it back to the desired window.
def zero_pad_or_mirror_epochs(epochs, zero_pad: bool, pad_length: int = 100):
"""
Zero-pad or mirror an MNE.Epochs object on both sides to avoid edge artifacts.
Args:
----
epochs: mne.Epochs
The input MNE Epochs object to be padded or mirrored.
zero_pad: bool
If True, zero-pad the data. If False, mirror the data.
pad_length: int, optional
Number of time points to pad (default is 100).
Returns:
-------
padded_epochs: mne.EpochsArray
The padded or mirrored MNE.EpochsArray object with adjusted time dimensions.
"""
data = epochs.get_data() # Extract the data from the epochs object
n_epochs, n_channels, n_times = data.shape
sfreq = epochs.info['sfreq'] # Get the sampling frequency
times = epochs.times # Original time array
# save metadata
metadata = epochs.metadata
# Initialize list to collect padded/mirrored data
padded_list = []
if zero_pad:
# Create a zero-padding array with shape (pad_length,)
zero_pad_array = np.zeros(pad_length)
# Loop over epochs and channels to zero-pad the data
for epoch in range(n_epochs):
ch_list = []
for ch in range(n_channels):
# Zero pad at beginning and end
ch_list.append(np.concatenate([zero_pad_array, data[epoch][ch], zero_pad_array]))
padded_list.append(ch_list)
else:
# Mirror data at the edges with a length equal to `pad_length`
for epoch in range(n_epochs):
ch_list = []
for ch in range(n_channels):
# Mirror pad at the beginning and end using `pad_length`
ch_list.append(np.concatenate([
data[epoch][ch][:pad_length][::-1], # Mirror the first `pad_length` points
data[epoch][ch], # Original data
data[epoch][ch][-pad_length:][::-1] # Mirror the last `pad_length` points
]))
padded_list.append(ch_list)
# Convert the list back to a numpy array with the correct shape
padded_data = np.array(padded_list)
# Adjust the time array to match the new data length
time_step = times[1] - times[0] # Time step based on sampling frequency
new_times = np.arange(times[0] - pad_length * time_step, times[-1] + pad_length * time_step, time_step)
# Create a new MNE EpochsArray with the padded/mirrored data
padded_epochs = mne.EpochsArray(padded_data, epochs.info, tmin=new_times[0], events=epochs.events, event_id=epochs.event_id)
# Add metadata back to the epochs object
padded_epochs.metadata = metadata
return padded_epochs