mne-tools / mne-python

MNE: Magnetoencephalography (MEG) and Electroencephalography (EEG) in Python
https://mne.tools
BSD 3-Clause "New" or "Revised" License
2.72k stars 1.32k forks source link

Annotations out of sync after concatenate_raws? #3509

Closed nwilming closed 8 years ago

nwilming commented 8 years ago

I'm working on a dataset where I cut 10 minute chunks out of a 1h recording, do some artifact detection and then concatenate the chunks that interest me. It seems that after the concatenation with concate_raws the artifact annotations are out of sync. I have a minimal example that creates an artificial raw structure that shows this behavior:

import mne
# Generate some random data
data = np.random.randn(5, 1000)*10e-12

sfreq = 100.
# Initialize an info structure
info = mne.create_info(
    ch_names=['MEG1', 'MEG2', 'MEG3', 'MEG4', 'MEG5'],
    ch_types=['grad']*5,
    sfreq=sfreq)

# Create 5 raw structures with annotations at 1 and 2 seconds into recording. Each
# raw has 10s of data
raws = []
for fs in [1000, 100, 12, 2001, 171]:
    ants = mne.annotations.Annotations(onset=[1., 2], duration=[.5, .5], description='x')
    r = mne.io.RawArray(data.copy(), info, first_samp=fs)
    r.annotations = ants
    r.annotations.onset += fs/sfreq
    raws.append(r)

conc = mne.concatenate_raws([r.copy() for r in raws])

# Correct annotations should be visible at 1,2, 11, 12, 21, 22, 31, 32, 41, and 42s
# when calling conc.plot(). 
corr_ants = mne.annotations.Annotations(
            onset=[1., 2, 11, 12, 21, 22, 31, 32, 41, 42], duration=[.5]*10, description='x')
corr_ants.onset += raws[0].first_samp/sfreq

assert all(conc.annotations.onset == corr_ants.onset) # This should fail

It seems that the way how annotations are combined is buggy. My guess is that the repeated call of mne.annotations._combine_annotations in mne.io.raw.append does not properly subtract and add the necessary offsets. Since this code is fairly central to mne and I don't understand the consequences of changing it I'm not proposing a PR. But for the example above the following code gives a valid annotation for the concatenated raw object:


def combine_annotations(annotations, first_samples, last_samples, sfreq):
    durations = [(1+l-f)/sfreq for f, l in zip(first_samples, last_samples)]
    offsets = cumsum([0] + durations[:-1])
    onsets = [(ann.onset-(fs/sfreq))+offset
                        for ann, fs, offset in zip(annotations, first_samples, offsets)]
    onsets = np.concatenate(onsets) + (first_samples[0]/sfreq)
    return mne.annotations.Annotations(onset=onsets,
        duration=np.concatenate([ann.duration for ann in annotations]),
        description=np.concatenate([ann.description for ann in annotations]))

d = combine_annotations([r.annotations for r in raws],
                        [r.first_samp for r in raws],
                        [r.last_samp for r in raws], sfreq)

assert all(d.onset == corr_ants.onset)
jasmainak commented 8 years ago

@nwilming indeed there is some problem with the code there.

@jaeilepp is on holiday till the end of the month. Feel free to make a PR with a minimal diff. Your code looks reasonable. Only comment is that you need to take care of the fact that some of the raw files being concatenated may not have annotations.

jaeilepp commented 8 years ago

I think this has to do with the mess with first_samp. I can take a look when I get back.

jaeilepp commented 8 years ago

I think the problem was just with the syncing with raw.plot. See https://github.com/mne-tools/mne-python/pull/3540.