Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eyetracking feature -- add channel with noisy periods set to nan #12416

Open
schekroud opened this issue Feb 2, 2024 · 4 comments
Open

eyetracking feature -- add channel with noisy periods set to nan #12416

schekroud opened this issue Feb 2, 2024 · 4 comments
Labels

Comments

@schekroud
Copy link

Describe the new feature or enhancement

currently the mne.preprocessing.eyetracking.interpolate_blinks function finds blink periods (with a buffer period around) and interpolates this period of noise.

One aspect of interest is not to interpolate, but to evaluate the amount of missing data, for example:

image

Rather than just evaluating the amount of missing data across the whole signal (e.g. with something like the suggestion of mne.annotations.quantify_coverage(raw, "BAD_BLINK"), actually adding in a copy of the pupil signal where rather than interpolate you set to nan, you can continue to epoch the data and then use epoch.plot_image to generate something like this.

The specific advantage of this is that your raw data being 'blinky' (lots of coverage in the raw signal) might not matter if those blinks are in e.g. ITI periods where you encourage participants to blink. In these cases, blinks might not contaminate the period you're interested in, thus the coverage of the raw isn't necessarily a problem. Similarly, you might decide to exclude a participant who doesn't have extensive coverage of blink periods, but systematically blinks in your time of interest (which you only find out after epoching).

Describe your proposed implementation

from line 102 in _pupillometry.py

# Linear interpolation
nterpolated_samples = np.interp(
raw.times[blink_indices],
raw.times[non_blink_indices],
raw._data[ci, non_blink_indices],
)
# Replace the samples at the blink_indices with the interpolated values
raw._data[ci, blink_indices] = interpolated_samples

#create nanned array
signal = raw._data[ci].copy()
signal[blink_indices] = np.nan #this now is not interpolated, but the full window of data that would be interpolated is set to nan
#code to add as a channel with name e.g. pupil_channel+'_nan' (not sure how this is easily implemented

Describe possible alternatives

in the custom code i use for work, i do something like this:

if add_nanchannel:
    tmp = mne.io.RawArray(signal.reshape(1, -1), info = data.copy().drop_channels(nonpup_chans).info)
    tmp.rename_channels({tmp.info.ch_names[0] : 'pupil_nan'}) #rename this channel (with data set to nan) before adding into raw object
    data.add_channels([tmp])

where it creates a new temporary RawArray with just one channel (the nanned pupil data) and takes a copy of an info structure from the raw object with just the pupil channel. It then renames this channel, and adds this channel (as the tmp RawArray object into the original data -- this is somewhat hacky because i couldn't find a simpler way of just adding a timeseries to the raw object and giving it a channel name. I'm assuming this is simpler with some base functions i am unaware of!

Additional context

No response

@schekroud schekroud added the ENH label Feb 2, 2024
@larsoner
Copy link
Member

larsoner commented Feb 2, 2024

Yes I think rather than adding some new function I would advise people just to do a variant of what you did. But no need for RawArray I think, something more like this:

    orig_pupil = data.copy().pick("pupil").rename_channels(dict(pupil="pupil_nan"))
    # ... interpolate, then
    data.add_channels([orig_pupil])

@larsoner
Copy link
Member

larsoner commented Feb 2, 2024

... in other words, stick with this custom bit for now and see if there is a larger audience for it before adding some new function/method. Could add it to an example/tutorial for now, though!

@schekroud
Copy link
Author

sure - only caveat is that this orig_pupil wouldn't represent the amount of data that is contaminated and would be interpolated. The original pupil timeseries would include the contaminated data around blinks that the buffer option is designed to remove. To do this you accurately still need to adjust the scope of your blink annotations - if this makes sense. Even just having the option to set to nan instead of only being able to interpolate is actually useful for this (not adding new channels)

@larsoner
Copy link
Member

larsoner commented Feb 6, 2024

We could add some general purpose mne.annotations.expand_annotations(...) that allowed expanding annotations matching some key by some amount. Internally the blink interpolation code could use it to expand the annotations, and it would take care of expanding the annot scope

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants