mne-tools / mne-python

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

ENH: upper & lower keywords for ICA's find_bads_eog #1654

Closed jona-sassenhagen closed 8 years ago

jona-sassenhagen commented 9 years ago

I hope I'm roughly following the appropriate protocol here, this is the first time I'm trying to make meaningful contributions to a software project. Or should I just directly make a pull request for finished (and properly tested :)) code?

I would like to add optional upper_eog and lower_eog keywords to find_bads_eog in ICA to conveniently correlate components with virtual HEOG/VEOG channels for slightly improved detection. Basically, detect EOG ICs not based on raw EOG, but based on the difference between supra- and infraorbital channels, or between LO1 and LO2 (as blink and horizontal EOG components, but not nearby brain sources invert polarity between these). This is simple, fast, somewhat more effective than using raw channels, and more convenient than manually setting up HEOG/VEOG difference channels and then dropping them again. Many labs use EOG difference channels to detect blinks, but as far as I understand it, the functionality is not yet explicitly included in find_bads_eog. Good idea?

Personally, in EEGLAB, I'm mostly using the degree of dipolarity to reject components, but programming a good dipole source localisation routine is beyond my capabilities.

agramfort commented 9 years ago

rather than a new parameter can you reuse an existing parameter adding an option?

please paste the code you write or would like to write.

jona-sassenhagen commented 9 years ago

Right, a dict for the ch_names parameter is smarter than introducing new keywords.

The routine as I have written it so far is rather small, but doesn't neatly integrate into the current function; I can't really use the aux functions because they take an object of type raw or epochs both to deduce source activity, and to load EOG data.

The routine itself can work something like this:

ch_names = dict(positive=['SO1', 'SO2'], negative=['IO1', 'IO2']) # example input

if isinstance(ch_names, dict):
    ic_timeseries = ica.get_sources(raw)._data # ica = self

    positive = ch_names['positive']
    negative = ch_names['negative']

    # get component indices; 
    #not using _get_eog_channel_index as not looking for default type=eog
    p_is = [i for i, ch in enumerate(inst.info['ch_names']) if ch in positive] # inst is of type raw
    n_is = [i for i, ch in enumerate(inst.info['ch_names']) if ch in negative]

    vchan_ts = np.mean(inst._data[p_is],0) - np.mean(inst._data[n_is],0)

    # vectorised corr. coefs; alternatively, loop using np.corrcoef
    rs = vcorrcoef(ic_timeseries,vchan_ts) 

out_ids = find_outliers(rs, threshold=threshold)

Example ipython notebook webarchive: https://www.dropbox.com/s/2d1toxtbq27or4l/upper_lower_demo.webarchive?dl=0

agramfort commented 9 years ago

sounds good to me. Note that there is a few functions for EOG artifact detection + rejection.

find_bads_eog find_eog_events create_eog_epochs

the API should be made consistent.

wmvanvliet commented 9 years ago

Is there any reason why you would ever want to work with the raw EOG if you have placed the electrodes in a bipolar setup?

You say:

[it's] more convenient than manually setting up HEOG/VEOG difference channels and then dropping them again.

It's sounds like we should have a function to easily create these HEOG/VEOG channels (or any virtual channel created by applying a bipolar reference). We don't have to drop these channels after the blink detection. It's usually very informative to keep them around. For example, when plotting the Evokeds, I like to plot the EOG channels as well, to see the influence of EOG on the EEG/MEG signals, maybe compare before and after any EOG filters.

dengemann commented 9 years ago

I was also thinking along the lines of what Marjin said. Wouldn't this be a nice job for rereferencing? We recently had a discussion somewhere

On 17 Nov 2014, at 08:49, Marijn van Vliet notifications@github.com wrote:

Is there any reason why you would ever want to work with the raw EOG if you have placed the electrodes in a bipolar setup?

You say:

[it's] more convenient than manually setting up HEOG/VEOG difference channels and then dropping them again.

It's sounds like we should have a function to easily create these HEOG/VEOG channels (or any virtual channel created by applying a bipolar reference). We don't have to drop these channels after the blink detection. It's usually very informative to keep them around. For example, when plotting the Evokeds, I like to plot the EOG channels as well, to see the influence of EOG on the EEG/MEG signals, maybe compare before and after any EOG filters.

— Reply to this email directly or view it on GitHub.

jona-sassenhagen commented 9 years ago

When correcting blinks and HEOG movement via ICA, you can treat the real (common-average) EOGs as regular channels. See e.g. http://www.ncbi.nlm.nih.gov/pubmed/17978035 After all, there is no substantial difference between e.g. LO1 or FP1 and F1. In contrast, Virtual/difference channels are incommensurable with the rest of the EEG; they don't have a real spatial location for example, so they make little sense in topoplots.

Does that rationale make sense to you?

wmvanvliet commented 9 years ago

I see.The 'raw' EOG channels are useful in some analysis schemes as well. That tells us that both the raw and the virtual channels are useful. The user should always be in control if then when these channels are dropped. You can join in on the discussion at #1635 if you like.

jona-sassenhagen commented 9 years ago

I don't actually see any use for virtual EOG channels once you have a proper ICA decomposition - there is nothing a difference channel can do that a good EOG IC combined with the real channels couldn't do better, isn't it?

Making this an option in find_bads_eog would simply be a convenience option, and one could even add a flag to keep the virtual channels. But by keeping it internal, the overhead from adding whole channels is avoided. Either way, you're right it's probably dependent on the outcome of #1635.

agramfort commented 9 years ago

@jona-sassenhagen have a look at #1713 and the set_bipolar_reference function to see if it does the job for you.

dengemann commented 9 years ago

Thinking about it a bit I actually like this idea. We do already a few things internally that make the ICA a nicer place. Why not this? Couldn't we just add dict input support.

ica.find_bads_eog(ch_name=dict(lower=['SO1', 'SO2'], upper=negative=['IO1', 'IO2']))

dengemann commented 9 years ago

I don't actually see any use for virtual EOG channels once you have a proper ICA decomposition - there is nothing a difference channel can do that a good EOG IC combined with the real channels couldn't do better, isn't it?

@jona-sassenhagen do you have something particular in mind? Btw. the other day I was thinking it would be cool to simply have a good set of training data, e.g. topographies and use a classification approach to finding EOG and ECG related components whenever you don't have designated channels. I think there's even been some package that does exactly that.

jona-sassenhagen commented 9 years ago

@dengemann what do you mean? I just can't think of any scenario where I'd use an EOG difference channel when I have an EOG IC.

I'll just make a pull request once #1713 is merged and you and @wmvanvliet can take a look if you think it's useful. It should be fairly minimal.

dengemann commented 9 years ago

what do you mean? I just can't think of any scenario where I'd use an EOG difference channel when > I have an EOG IC.

it sounds as if you had some approach in mind to identify EOG components or reconstruct EOG time courses without actual EOG channels. How do you automatically identify your good EOG component?

dengemann commented 9 years ago

and you and @wmvanvliet can take a look if you think it's useful. It should be fairly minimal.

yeah, sure!