How to make mne raw from LSL in realtime

Dear MNE user,

Currently, I am using Zeto dry EEG headset and try to read the data in real-time through LSL. After reading the data in real-time, I would like process it with mne in real-time. If I enter the data of sample in LSL code into data variable as follows, can I make raw data of mne?

raw = mne.io.RawArray(data, info)

from pylsl import StreamInlet, resolve_stream

def main():
# first resolve an EEG stream on the lab network
print(“looking for an EEG stream…”)
streams = resolve_stream(‘type’, ‘EEG’)

# create a new inlet to read from the stream
inlet = StreamInlet(streams[0])

while True:
    # get a new sample (you can also omit the timestamp part if you're not
    # interested in it)
    sample, timestamp = inlet.pull_chunk()
    print(timestamp, sample)

if name == ‘main’:
main()

It’s not easy but here is how I did it in one of my projects. @kyuwanchoi

  1. retrieve the sampling rate from info.nominal_srate() looping on for info in pylsl.resolve_streams() and making sure you catch the right one by checking info.name() or other parameters
  2. define the SteamInlet with the max_buflen parameter matching the max number of seconds you want to retrieve, for me it was 2 seconds pylsl.StreamInlet(info, max_buflen=2000, processing_flags=pylsl.proc_clocksync | pylsl.proc_dejitter)
  3. Create an empty numpy buffer or np.ndarray(N electrodes, N seconds x sampling rate)
  4. This is still a mistery to me how to do it right, but periodically retrieve your data properly in a numpy array the size of the buffer.
  5. create a mne.io.RawArray. I think you have to use y = np.transpose(array) because lsl and MNE have different structure. Then create a raw off your info and data raw = mne.io.RawArray(data=y, info=data_inlet.mne_info, verbose=False)
  6. Try to find a way to rename the channels. Also, make sure to put a filename to prevent a bug in plotting your data: raw._filenames = [‘lsl_is_not_a_file_duuh’]
  7. Now you don’t want to reinitialize any raw. Copy your raw and perform stuff you want to do on it. Just make a copy before reusable_raw = raw.copy()
  8. Erase the data from it reusable_raw ._data = None
  9. Pickle the raw, it will keep information stored along with the raw: pickled_raw= pickle.dumps(reusable_raw ). Of course don’t do this after you performed any data transformation, but before.
  10. on the next iteration, load the pickled raw raw = pickle.loads(pickled_raw). and put the new data directly inside raw._data = y. Enjoy massive performance gains by using the pickle trick (“I’m Pickle riiick”)

Good luck :slight_smile:

2 Likes

Hi lokinou, I really appreciate your support! Your reply is very helpful for me. By the way, do you have the code? I want to see the code.

Kyu

Glad you appreciate @kyuwanchoi . I can’t share the code, that’s why you got a nice description of how to do it :).

Please let me know if you managed to get through it.

If you want, you can have a look at NeuroDecode, especially to my fork which improves the low-level LSL communication classes and methods. I will soon rename the library to bsl and publish it on pip.

Basically, it would look like this for EEG:

import mne
from neurodecode import StreamReceiver

sr = StreamReceiver(stream_name='Name')

sr.acquire()
data, timestamps = sr.get_window() # data is samples x channels
data *= 1e-6 # convert to Volts, to be adapted.
info = mne.create_info(
    ch_names=sr.streams['Name'].ch_list, 
    sfreq=sr.streams['Name'].sample_rate, 
    ch_types=['eeg']*len(sr.streams['Name'].ch_list))
raw = mne.io.RawArray(data.T, info) # expect channels x samples

The StreamReceiver allows you to connect to multiple LSL streams and to retrieve data from one or more of those streams. It basically is a wrapper to simplify the use of pylsl.

For recording, you have a StreamRecorder provided in the lib.

1 Like