Note
Go to the end to download the full example code.
Automatic vs Manual acquisitionπ
The StreamLSL
object offers 2 mode of acquisition: automatic or
manual. In automatic mode, the stream object 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.
import uuid
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"
source_id = uuid.uuid4().hex
player = PlayerLSL(fname, chunk_size=200, source_id=source_id).start()
player.info
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, source_id=source_id).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: c7421cb797524613932176f400b8e4f9)>
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, source_id=source_id).connect(acquisition_delay=None)
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()
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: c7421cb797524613932176f400b8e4f9)>
<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 12.604 seconds)
Estimated memory usage: 265 MB