Automatic vs Manual acquisitionπŸ”—

The StreamLSL object offers 2 mode of acquisition: automatic or manual. In automatic mode, the stream objecdt acquires new chunks of samples at a regular interval. In manual mode, the user has to call the acquire() to acquire new chunks of samples from the network. The automatic or manual acquisition is selected via the acquisition_delay argument of connect():

  • a non-zero positive integer value will set the acquisition to automatic mode with the specified delay in seconds.

  • 0 will set the acquisition to manual mode.

Automatic acquisitionπŸ”—

When the stream is set to automatically acquire new samples at a regular interval, a background thread is created with concurrent.futures.ThreadPoolExecutor. The background thread is periodically receives a job to acquire new samples from the network.

Important

If the main thread is hogging all of the CPU resources, the delay between two acquisition job might be longer than the specified delay. The background thread will always do its best to acquire new samples at the specified delay, but it is not able to do so if the CPU is busy.

from time import sleep

from matplotlib import pyplot as plt

from mne_lsl.datasets import sample
from mne_lsl.player import PlayerLSL
from mne_lsl.stream import StreamLSL

# create a mock LSL stream for this tutorial
fname = sample.data_path() / "sample-ant-raw.fif"
player = PlayerLSL(fname, chunk_size=200).start()
player.info
General
Measurement date Unknown
Experimenter mne_anonymize
Participant Unknown
Channels
Digitized points Not available
Good channels 63 EEG, 2 EOG, 1 Galvanic skin response, 1 ECG, 1 Stimulus
Bad channels None
EOG channels vEOG, hEOG
ECG channels ECG
Data
Sampling frequency 1024.00 Hz
Highpass 0.00 Hz
Lowpass 512.00 Hz


Note

A chunk_size of 200 samples is used here to ensure stability and reliability while building the documentation on the CI. In practice, a chunk_size of 200 samples is too large to represent a real-time application.

stream = StreamLSL(bufsize=2).connect(acquisition_delay=0.1)
sleep(2)  # wait for new samples
print(f"New samples acquired: {stream.n_new_samples}")
stream.disconnect()
New samples acquired: 3200

<Stream: OFF | MNE-LSL-Player (source: MNE-LSL)>

Manual acquisitionπŸ”—

In manual acquisition mode, the user has to call the acquire() to get new samples from the network. In this mode, all operation happens in the main thread and the user has full control over when to acquire new samples.

stream = StreamLSL(bufsize=2).connect(acquisition_delay=0)
sleep(2)  # wait for new samples
print(f"New samples acquired (before stream.acquire()): {stream.n_new_samples}")
stream.acquire()
print(f"New samples acquired (after stream.acquire()): {stream.n_new_samples}")
New samples acquired (before stream.acquire()): 0
New samples acquired (after stream.acquire()): 2048

However, it is also now up to the user to make sure he acquires new samples regularly and does not miss part of the stream. The created StreamInlet has its buffer set to the same value as the StreamLSL object.

stream.acquire()
data1, ts1 = stream.get_data(picks="Cz")
sleep(4)  # wait for 2 buffers
stream.acquire()
data2, ts2 = stream.get_data(picks="Cz")

f, ax = plt.subplots(1, 1, layout="constrained")
ax.plot(ts1 - ts1[0], data1.squeeze(), color="blue", label="acq 1")
ax.plot(ts2 - ts1[0], data2.squeeze(), color="red", label="acq 2")
ax.legend()
ax.set_xlabel("Time (s)")
ax.set_ylabel("EEG amplitude")
plt.show()
30 stream manual

Free resourcesπŸ”—

When you are done with a PlayerLSL or StreamLSL, don’t forget to free the resources they both use to continuously mock an LSL stream or receive new data from an LSL stream.

<Stream: OFF | MNE-LSL-Player (source: MNE-LSL)>
<Player: MNE-LSL-Player | OFF | /home/runner/mne_data/MNE-LSL-data/sample/sample-ant-raw.fif>

Total running time of the script: (0 minutes 16.583 seconds)

Estimated memory usage: 108 MB

Gallery generated by Sphinx-Gallery