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

XDF reading: merging streams via resampling impacts channels that contain NaN #471

Open
sappelhoff opened this issue Jan 7, 2025 · 4 comments

Comments

@sappelhoff
Copy link
Contributor

sappelhoff commented Jan 7, 2025

  • Tobii eyetracker devices may produce time series data that contain NaN values (for example pupil dilation when the eye is closed)
  • When streaming several Tobii eyetracker streams to LSL and recording in XDF, one may eventually want to merge the separate eyetracker streams into one MNE raw object
  • In theory, this is possible with mnelab.io.xdf.read_raw_xdf
  • however, the presence of NaN values in some channels of the streams result in the entire channel being NaN

I suspect this is due to the resampling done here:

x_new = scipy.signal.resample(x_old, len_new, axis=0)

See this proof of principle:

import numpy as np
import scipy.signal

x = np.arange(100) * 1.0
x[1] = np.nan
y = scipy.signal.resample(x, 90, axis=0)
assert np.isnan(y).all()  # this passes

I don't really have an idea how to prevent this. Would masking NaNs with "placeholder values" prior to resampling be an option? and then converting them back to NaN after resampling?

@cbrnr
Copy link
Owner

cbrnr commented Jan 7, 2025

Maybe some domain-specific preprocessing could be an option? Would using the last value instead of NaN make sense? I think some sort of interpolation will be necessary before resampling.

@sappelhoff
Copy link
Contributor Author

Maybe some domain-specific preprocessing could be an option?

Yes, a current solution is to read each stream separately into mne Raw objects (via mnelab), then do the processing, then resample and crop the raw objects, and finally call raw.add_channels() to concatenate the objects

Perhaps this is the only sensible way to avoid unnecessarily blowing up the reading code here with an edge case 🤔

@cbrnr
Copy link
Owner

cbrnr commented Jan 8, 2025

I mean, if this is a common operation for eyetracking streams it might be worth including it as an option. If it's really an edge case, not so much. Can you provide more details on how you actually "do the processing" for these kinds of signals? How do you get rid of the NaNs?

@sappelhoff
Copy link
Contributor Author

I mean, if this is a common operation for eyetracking streams it might be worth including it as an option.

Yes, unlike in EEG or other time series, NaN values are to be expected in eye tracking, because when the tracker loses the eyes (e.g., due to a blink), a NaN value gets assigned.

If it's really an edge case, not so much.

I thought the edge case here is probably having two separate eye-tracking streams that need to be merged. Most people will only have one eye-tracking stream, and will thus only have to resample if they want to combine it with another stream.

But then again, many users MIGHT want to combine their eye-tracking and EEG streams. So perhaps this is all just a valid case.

Can you provide more details on how you actually "do the processing" for these kinds of signals? How do you get rid of the NaNs?

For now, I want to keep the NaNs in my mne raw object, because I eventually want to interpolate these values with dedicated functions, see also:

I am currently:

  • reading each stream separately (no resampling)
  • checking if they are approximately aligned (same signal length)
    • crop the longer signal to the length of the shorter one if there is no alignment (usually there is only a few samples of misalignment, which is not ideal but maybe OK)
  • call add_channels to combine the recordings

☝️ I am not happy with this and will need to revise this in the future.

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

No branches or pull requests

2 participants