StreamPlayer: simulate an LSL stream

Testing designs for online paradigm can be difficult. Access to hardware measuring real-time brain signals can be limited and time-consuming. With a StreamPlayer, a fake data stream can be created and used to test code and experiment designs.

# Authors: Mathieu Scheltienne <mathieu.scheltienne@fcbg.ch>
#
# License: LGPL-2.1

Warning

Both StreamPlayer and StreamRecorder create a new process to stream or record data. On Windows, mutliprocessing suffers a couple of restrictions. The entry-point of a multiprocessing program should be protected with if __name__ == '__main__': to ensure it can safely import and run the module. More information on the documentation for multiprocessing on Windows.

This example will use a sample EEG resting-state dataset that can be retrieve with bsl.datasets. The dataset is stored in the user home directory, in the folder bsl_data.

import time

from bsl import StreamPlayer, datasets
from bsl.lsl import resolve_streams
from bsl.triggers import TriggerDef

Starting a StreamPlayer

A StreamPlayer requires at least 2 arguments:

  • stream_name, indicating a the name of the stream on the LSL network.

  • fif_file, path to a valid Raw fif file.

stream_name = "StreamPlayer"
fif_file = datasets.eeg_resting_state.data_path()
print(fif_file)
/home/runner/bsl_data/eeg_sample/resting_state-raw.fif

Instance

To create an LSL stream, create a StreamPlayer and use the start method.

Note

By default, the start method is blocking and will wait for the streaming to start on the network. This behavior can be changed with the blocking argument.

player = StreamPlayer(stream_name, fif_file)
player.start()
print(player)
<Player: StreamPlayer | ON | /home/runner/bsl_data/eeg_sample/resting_state-raw.fif>

To verify if the stream is accessible on the network, use directly pylsl:

streams = [stream.name for stream in resolve_streams()]
print(streams)
['StreamPlayer']

To stop the streaming, use the stop method.

player.stop()
print(player)
<Player: StreamPlayer | OFF | /home/runner/bsl_data/eeg_sample/resting_state-raw.fif>

Context manager

A StreamPlayer can also be used as a context manager with a with statement. The context manager takes care of starting and stopping the LSL stream.

with StreamPlayer(stream_name, fif_file):
    streams = [stream.name for stream in resolve_streams()]
print(streams)
['StreamPlayer']

CLI

Finally, a StreamPlayer can be called from the terminal with a command line. This is the recommended way of starting a StreamPlayer. Example assuming the current working directory is bsl_data:

$ bsl_stream_player StreamPlayer eeg_sample\resting_state-raw.fif

Hitting ENTER will stop the StreamPlayer.

StreamPlayer

Additional arguments

A StreamPlayer has 4 optional arguments:

  • repeat, indicating the number of time the data in the file is repeated.

  • trigger_def, either the path to a TriggerDef definition file or a TriggerDef instance, improving the logging of events found in the Raw fif file.

  • chunk_size, indicating the number of samples push at once on the LSL outlet.

  • high_resolution, indicating if sleep or perf_counter is used to wait between 2 push on the LSL outlet.

repeat

repeat is set by default to +inf, returning to the beginning of the data in the Raw fif file each time the entire file has been 2 streamed. To limit the number of replay, an int can be passed.

Note

eeg_resting_state_short is similar to eeg_resting_state but last 2 seconds instead of 40 seconds.

<Player: StreamPlayer | ON | /home/runner/bsl_data/eeg_sample/resting_state_short-raw.fif>

The dataset is streamed only once. A call to the stop method is not necessary.

time.sleep(2)  # duration of this dataset.
print(player)
<Player: StreamPlayer | OFF | /home/runner/bsl_data/eeg_sample/resting_state_short-raw.fif>

trigger_def

TriggerDef can be used to assign a user-readable string to an event id. Providing a valid bsl.triggers.TriggerDef to a StreamPlayer improves the logging of events found on the TRIGGER channel.

Note

eeg_auditory_stimuli contains a` alternation of rest events (1) lasting 1 second and of auditory stimuli events (4) lasting 0.8 second.

fif_file = datasets.eeg_auditory_stimuli.data_path()
player = StreamPlayer(stream_name, fif_file)
player.start()
print(player)

# wait a bit to get some events logged
time.sleep(4)

# stop
player.stop()
print(player)
<Player: StreamPlayer | ON | /home/runner/bsl_data/eeg_sample/auditory_stimuli-raw.fif>
<Player: StreamPlayer | OFF | /home/runner/bsl_data/eeg_sample/auditory_stimuli-raw.fif>

By default, the logging of events uses the ID with Events: ID. If a bsl.triggers.TriggerDef is provided, the logging message will include the corresponding event name if it exists with Events: ID (NAME).

tdef = TriggerDef()
tdef.add("rest", 1)

player = StreamPlayer(stream_name, fif_file, trigger_def=tdef)
player.start()
print(player)

# wait a bit to get some events logged
time.sleep(4)

# stop
player.stop()
print(player)
<Player: StreamPlayer | ON | /home/runner/bsl_data/eeg_sample/auditory_stimuli-raw.fif>
<Player: StreamPlayer | OFF | /home/runner/bsl_data/eeg_sample/auditory_stimuli-raw.fif>

Note

A path to a valid .ini trigger definition file can be passed instead of a TriggerDef instance. The file is read with configparser and has to be structured as follows:

[events]
event_str_1 = event_id_1   # comment
event_str_2 = event_id_2   # comment

Example:

[events]
rest = 1
stim = 2

chunk_size

chunk_size defines how many samples are pushed at once on the LSL oulet each time the StreamPlayer sends data. The default 16 should work most of the time. A warning is emitted if the value is different from the usual 16 or 32.

high_resolution

Between 2 push of samples on the LSL outlet, the StreamPlayer waits. This sleep duration can be achieved either with sleep or with perf_counter. The second is more precise, but also uses more CPU.

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

Estimated memory usage: 264 MB

Gallery generated by Sphinx-Gallery