Concatenating epochs with various times

I want to load single epochs exported from Brainstorm (data shared by collaborator) and concatenate them in MNE.
So far I am able to create single epochs, but when I try to concatenate them I am receiving

raise ValueError("Epochs must have same times")

Epochs times are for example [0, 4.99s], [5, 9.99] et cetera - 5s subsequent chunks.
My code is below


def load_single_mat_epoch(mat_file):
    # Load the .mat file
    mat_data = sio.loadmat(mat_file)

    # Extract relevant data
    data = mat_data["Value"]  # Transpose to shape (1, 68, 3000)
    data = data[np.newaxis, :, :]
    times = mat_data["Time"].flatten()
    print(f"File: {Path(mat_file).name}, Time array length: {len(times)}")

    region_struct = mat_data["Atlas"][0, 0]["Scouts"]["Label"].ravel().tolist()
    ch_names = [item.item() for item in region_struct]
    sfreq = 600.0

    info = mne.create_info(ch_names=ch_names, sfreq=sfreq, ch_types="misc")
    # Create events array for this single epoch
    start_idx = int(times[0]*sfreq)
    events = np.array([[start_idx, 0, 1]])

    return mne.EpochsArray(data, info, events, tmin=times[0])


def build_mne_epochs_from_matlab_files(file_paths):
    """Build MNE Epochs object from multiple MATLAB files."""
    epochs_list = []
    for file_path in file_paths:
        try:
            epoch = load_single_mat_epoch(file_path)
            epochs_list.append(epoch)
        except Exception as e:
            print(f"Error loading {file_path}: {e}")

    if not epochs_list:
        raise ValueError("No valid epochs were loaded.")

    # Concatenate all loaded epochs
    # return mne.concatenate_epochs(epochs_list, add_offset=False, on_mismatch="warn")
    return epochs_list

Should I remove the times imported from Matlab? How I can retain this information about timing in an other way?

Hi Daniel,

I believe your issue comes from the following line:

mne.EpochsArray(data, info, events, tmin=times[0])

I replicate your error with the following dummy data


# Create some dummy metadata
sfreq = 200  # in Hertz
times = np.linspace(0, 1, sfreq, endpoint=False)
sine = np.sin(20 * np.pi * times)
cosine = np.cos(10 * np.pi * times)
data = np.array([sine, cosine])
# make data such as N_Epochs, N_chan, N_times
data = np.array(
    [
        [0.2 * sine, 1.0 * cosine],
        [0.4 * sine, 0.8 * cosine],
        [0.6 * sine, 0.6 * cosine],
        [0.8 * sine, 0.4 * cosine],
        [1.0 * sine, 0.2 * cosine],
    ]
)

brainstorm_times = np.array(
    [
        [0, 4.99], 
        [5, 9.99],
        [10, 14.99],
        [15, 19.99],
        [20, 24.99]
    ]
)

epochs_list = []
ch_names = ['chan1', 'chan2']
info = mne.create_info(ch_names=ch_names, sfreq=sfreq, ch_types="misc")

for i, (x, time) in enumerate(zip(data, brainstorm_times)):
    print(f'Epochs {i}')
    start_idx = int(time[0]*sfreq)
    print(start_idx)
    
    x1 = x[np.newaxis, :, :] # reshape to epochs, channel, times
    events = np.array([[start_idx, 0, 1]])
    simulated_epochs = mne.EpochsArray(x1, info, events, tmin=time[0]) # by default tmin is 0
    epochs_list.append(simulated_epochs)
    
epochs = mne.concatenate_epochs(epochs_list, add_offset=False, on_mismatch="warn")

This will result to the error you reported.

But if you pay attention to the epochs_list variable, you can see that actually the epochs do not have the same time. Indeed, they all are an epochs of 1 seconds, but with a different start of time. The first epoch will go from 0 to 1s, but the last will go from 20 to 21s.

[<EpochsArray |  1 events (all good), 0 – 0.995 s, baseline off, ~10 kB, data loaded,
  '1': 1>,
 <EpochsArray |  1 events (all good), 5 – 5.995 s, baseline off, ~10 kB, data loaded,
  '1': 1>,
 <EpochsArray |  1 events (all good), 10 – 10.995 s, baseline off, ~10 kB, data loaded,
  '1': 1>,
 <EpochsArray |  1 events (all good), 15 – 15.995 s, baseline off, ~10 kB, data loaded,
  '1': 1>,
 <EpochsArray |  1 events (all good), 20 – 20.995 s, baseline off, ~10 kB, data loaded,
  '1': 1>]

Here you want to have all your epochs starting at the same time by doing this

simulated_epochs = mne.EpochsArray(x1, info, events, tmin=0) # by default tmin is 0

and if you check the epochs_list variable afterwards it should look like the following:

[<EpochsArray |  1 events (all good), 0 – 0.995 s, baseline off, ~10 kB, data loaded,
  '1': 1>,
 <EpochsArray |  1 events (all good), 0 – 0.995 s, baseline off, ~10 kB, data loaded,
  '1': 1>,
 <EpochsArray |  1 events (all good), 0 – 0.995 s, baseline off, ~10 kB, data loaded,
  '1': 1>,
 <EpochsArray |  1 events (all good), 0 – 0.995 s, baseline off, ~10 kB, data loaded,
  '1': 1>,
 <EpochsArray |  1 events (all good), 0 – 0.995 s, baseline off, ~10 kB, data loaded,
  '1': 1>]

Now you should be allow to concatenate (at least the following worked out for me).

epochs = mne.concatenate_epochs(epochs_list, add_offset=False, on_mismatch="warn")

And as for your question about retaining the timing, you are actually doing it already by giving the events (to my understanding).
You can double check at your final epochs, by doing epochs.events, this should provide you the information about when an epochs start, and you can visualise them with epochs.plot
Hope this helped.

Best,
Fayed