Data Stream API¶
Streams are the basic building blocks of any PhysioLabXR paradigm. Streams are connected to data sources that produces data over time. PhysioLabXR has the following hooks to connect to data sources and create streams:
Network: connects to data streams broadcasted through the local area network. PhysioLabXR supports two popular network APIs for streaming experiment data: LSL and ZMQ
Python API: for devices that has Python API, such as brainflow for OpenBCI devices, users can write their own data source script and connect it to PhysioLabXR through the scripting interface. More information here.
Video and audio devices: for devices recognized by the OS as webcams or microphones, they will automatically appear on the list of devices available to stream in PhysioLabXR. More information here.
Introduction¶
Lab Streaming Layer (LSL)¶
LabStreamingLayer (LSL) is a network protocol for streaming time series data, mostly used for physiological experiments. LSL has the following characteristics:
Native support for many popular devices (more information here on how to connect supported device with LSL).
LSL is a cross-platform protocol, and has bindings for many programming languages. You can create your own data source script in your platform and PhysioLabXR will take care of the receiving end.
LSL outlets can carry meta information of your stream such as sampling rate and channel names.
LSL has API in many programming languages. For a more comprehensive guide, check out the examples in LSL documentation. Here, we will show two simple example of how to create an LSL outlet in Python and Unity.
Note
LSL is not suited for streams with a large number of channels (e.g., video), because each channel carries its own meta information and transmitting the meta information every frame creates a large overhead. For such streams, we recommend using ZMQ.
ZeroMQ (ZMQ)¶
ZMQ is a messaging library for exchanging data between applications. ZMQ has the following characteristics:
ZMQ is a lightweight messaging library, and it is suited for streaming data with a large number of channels (e.g., video).
Like LSL, ZMQ is a cross-platform protocol, and has bindings for many programming languages.
ZMQ supports many different socket patterns. PhysioLabXR supports the subscriber-publisher socket pattern, because this pattern is more scalable when the cardinality of data is high and less error-prone in case of either the publisher or subscriber process crashes. You can find a more detailed explanation of the socket patterns here.
Write Your Own Data Source In Python¶
Using LSL¶
To run the example, make sure you have Python installed. Then, install the physiolabxr package by running the following command in your terminal:
pip install physiolabxr
Alternatively, you can just install the pylsl package by running the following command in your terminal:
pip install pylsl
Note
If you installed pylsl, you may need to add liblsl to your site-packages for it to work. Follow the instructions here.
On the other hand, if you installed physiolabxr, the app will automatically add liblsl to your site-packages first time you run it.
Use the command physiolabxr to run it from your terminal after pip installing it.
Now we are ready to run the example.
Create a new Python file and copy the following code into it:
import time
from pylsl import StreamInfo, StreamOutlet, local_clock
from random import random as rand
# create a new stream info and outlet
stream_name = 'python_lsl_my_stream_name'
n_channels = 8
# using the local_clock() to track elapsed time
start_time = local_clock()
# track how many samples we have sent
sent_samples = 0
# set the sampling rate to 100 Hz
nominal_sampling_rate = 100
info = StreamInfo('stream_name', 'my_stream_type', n_channels, nominal_sampling_rate, 'float32',
'my_stream_id')
outlet = StreamOutlet(info)
# send data
while True:
# calculate how many samples we need to send since the last call
elapsed_time = local_clock() - start_time
required_samples = int(nominal_sampling_rate * elapsed_time) - sent_samples
for sample_ix in range(required_samples):
mysample = [rand() * 10 for _ in range(n_channels)]
# now send it
outlet.push_sample(mysample)
sent_samples += required_samples
# now send it and wait for a bit before trying again.
time.sleep(0.01)
Run the above script with:
python <your-file-name>.py
You can find this script in PhysioLabXR’s GitHub repository examples-WriteYourOwnDataSourceExamples.
Using ZMQ¶
Write Your Own Data Source In Unity (C#)¶
You can Download
Using LSL¶
Using ZMQ¶
Visualize Your Data¶
Now that you have a data source running, you can visualize your data in PhysioLabXR by following the steps below:
Visualize LSL Data¶
Open PhysioLabXR (download the App here if you haven’t already).
Type in the name of the stream you created in the script (e.g.
python_lsl_my_stream_nameorunity_lsl_my_stream_namein the example above) inAdd Stream Widget.Select
LSLas the stream type in the dropdown just right where you put in the stream name.Click on the
Addbutton, a stream widget will be added to the main window. The widget will haveicon on the bottom. This means that the stream is available on the network. But we are not receiving and plotting any data yet.
You will be prompted to auto-set the number of channels to what’s being streamed from your script above. This is because the default number of channels is 1 for newly added stream widget. Click on
Yesto auto-set the number of channels.The
icon will change to
icon. You will see the data being plotted in line chart (explore other visualization method).
Using LSL¶
LabStreamingLayer (LSL) is a network protocol for streaming time series data, mostly used for physiological experiments. LSL has the following characteristics:
Native support for many popular devices (more information here on how to connect supported device with LSL).
LSL is a cross-platform protocol, and has bindings for many programming languages. You can create your own data source script in your platform and PhysioLabXR will take care of the receiving end.
LSL outlets can carry meta information of your stream such as sampling rate and channel names.
Note
LSL is not suited for streams with a large number of channels (e.g., video), because each channel carries its own meta information and transmitting the meta information every frame creates a large overhead. For such streams, we recommend using ZMQ.
Write your LSL data source¶
LSL has API in many programming languages. For a more comprehensive guide, check out the examples in LSL documentation. Here, we will show two simple example of how to create an LSL outlet in Python and Unity.
Python¶
To run the example, make sure you have Python installed. Then, install the physiolabxr package by running the following command in your terminal:
pip install physiolabxr
Alternatively, you can just install the pylsl package by running the following command in your terminal:
pip install pylsl
Note
If you installed pylsl, you may need to add liblsl to your site-packages for it to work. Follow the instructions here.
On the other hand, if you installed physiolabxr, the app will automatically add liblsl to your site-packages first time you run it.
Use the command physiolabxr to run it from your terminal after pip installing it.
Now, create a new Python file and copy the following code:
import time
import numpy as np
from pylsl import StreamInfo, StreamOutlet
# create a new stream info and outlet
n_channels = 8
info = StreamInfo('my_stream_name', 'my_stream_type', n_channels, 100, 'float32', 'my_stream_id')
outlet = StreamOutlet(info)
# send data
while True:
# make a new random 8-channel sample; this is converted into a pylsl.vectorf (the data type that is expected by push_sample)
mysample = np.random.randn(n_channels).tolist()
# now send it and wait for a bit
outlet.push_sample(mysample)
time.sleep(0.01)
Run the above script with:
python <your-file-name>.py
Unity¶
Make sure you have Unity installed. You can clone this `example project <
Similar to the previous Python example, the following script will create an LSL Outlet in Unity and broadcast the data to the local area network.
Visualize the stream in PhysioLabXR¶
Open PhysioLabXR (download the App here if you haven’t already).
Type in the name of the stream you created in the script (my_stream_name in the example above) in Add Stream Widget.
Select LSL as the stream type in the dropdown just right where you put in the stream name.
Click on the Add button, a stream widget will be added to the main window. The widget will have
icon on the bottom. This means that the stream is available on the network. But we are not receiving and plotting any data yet.
Click on the button to start streaming the data.
You will be prompted to auto-set the number of channels to what’s being streamed from your script above. This is because
the default number of channels is 1 for newly added stream widget. Click on Yes to auto-set the number of channels.
The icon will change to
icon.
You will see the data being plotted in line chart (explore other visualization method).
Using ZMQ¶
ZMQ is a messaging library for exchanging data between applications. ZMQ has the following characteristics:
ZMQ is a lightweight messaging library, and it is suited for streaming data with a large number of channels (e.g., video).
Like LSL, ZMQ is a cross-platform protocol, and has bindings for many programming languages.
ZMQ supports many different socket patterns. PhysioLabXR supports the subscriber-publisher socket pattern, because this pattern is more scalable when the cardinality of data is high and less error-prone in case of either the publisher or subscriber process crashes. You can find a more detailed explanation of the socket patterns here.
Write your ZMQ data source¶
Similar to LSL, ZMQ can be used in many programming languages. Here, we will show a simple example of how to create a ZMQ publisher to send data in Python and Unity (C#). The following two section will show how to create ZMQ data source in Python and C# for your reference. For this example, you can choose your preferred language to create the data source.
The following code will send image stream with random data to PhysioLabXR through ZMQ.
To create the ZMQ data source in Python, first make sure you have Python installed. Then, install the pyzmq package by running the following command in your terminal:
pip install pyzmq
Now, create a new Python file and copy the following code:
import time
from collections import deque
import numpy as np
import zmq
topic = "my_stream_name" # name of the publisher's topic / stream name
srate = 15 # we will send 15 frames per second
port = "5557" # ZMQ port number
# we will send a random image of size 400x400 with 3 color channels
c_channels = 3
width = 400
height = 400
n_channels = c_channels * width * height
context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:%s" % port)
# next make an outlet
print("now sending data...")
send_times = deque(maxlen=srate * 10)
start_time = time.time()
sent_samples = 0
while True:
elapsed_time = time.time() - start_time
required_samples = int(srate * elapsed_time) - sent_samples
if required_samples > 0:
samples = np.random.rand(required_samples * n_channels).reshape((required_samples, -1))
samples = (samples * 255).astype(np.uint8)
for sample_ix in range(required_samples):
mysample = samples[sample_ix]
socket.send_multipart([bytes(topic, "utf-8"), np.array(time.time()), mysample]) # send frame in the order: topic, timestamp, data
send_times.append(time.time())
sent_samples += required_samples
# now send it and wait for a bit before trying again.
time.sleep(0.01)
if len(send_times) > 0:
fps = len(send_times) / (np.max(send_times) - np.min(send_times))
print("Send FPS is {0}".format(fps), end='\r')
print(f'current timestamp is {time.time()}', end='\r', flush=True)
Run the above script with:
python <your-file-name>.py
The following code will send image stream from a Unity camera to PhysioLabXR through ZMQ.
To make the C# script work, you need install NetMQ, the C# binding for ZMQ. NetMQ is distributed with NuGet, which is a package manager for .NET. Download the NuGetForUnity package from this page. Download the file named NuGetForUnity.<some-version>.unitypackage and import it to your Unity project.
Once you have NuGetForUnity installed, you will have a new option in your Unity Editor’s context menu called NuGet. Go to NuGet > Manage NuGet Packages.
In the NuGet window, search for NetMQ and install it. This will install NetMQ and its dependencies.
Attach the following script to a game object in your scene. Set the script’s public field captureCamera to a camera in the scene whose image you want to send.
using System.Collections;
using UnityEngine;
using AsyncIO;
using NetMQ;
using NetMQ.Sockets;
using System;
public class TestZMQScript : MonoBehaviour
{
[Header("In-game Objects")]
public Camera captureCamera; // in your editor, set this to the camera you want to capture
[Header("Camera Capture Image Size")]
public int imageWidth = 400;
public int imageHeight = 400;
[Header("Runtime Parameters")]
public float srate = 15f;
// objects to hold the image data;
RenderTexture tempRenderColorTexture;
Texture2D colorImage;
[Header("Networking Fields")]
public string tcpAddress = "tcp://localhost:5557";
public string topicName = "CameraCapture";
PublisherSocket socket;
[Header("Networking Information (View-only)")]
public long imageCounter = 0;
public float fps = 0;
private void Start()
{
// check if capture camera has been set
if (captureCamera == null)
{
Debug.LogError("CameraCaptureServer: captureCamera is not set. Please set it in the editor.");
return;
}
tempRenderColorTexture = new RenderTexture(imageWidth, imageHeight, 24, RenderTextureFormat.ARGB32)
{
antiAliasing = 4
};
colorImage = new Texture2D(imageWidth, imageHeight, TextureFormat.RGB24, false, true);
ForceDotNet.Force();
socket = new PublisherSocket(tcpAddress);
StartCoroutine(UploadCapture(1f / srate));
}
IEnumerator UploadCapture(float waitTime)
{
while (true)
{
yield return new WaitForSeconds(waitTime);
float frameStartTime = Time.realtimeSinceStartup;
byte[] imageBytes = encodeColorCamera();
double timestamp = Time.unscaledTime;
socket.SendMoreFrame(topicName).SendMoreFrame(BitConverter.GetBytes(timestamp)).SendFrame(imageBytes);
imageCounter++;
float frameEndTime = Time.realtimeSinceStartup;
fps = 1f / (frameEndTime - frameStartTime);
}
}
public byte[] encodeColorCamera()
{
//render color camera and save bytes
captureCamera.targetTexture = tempRenderColorTexture;
RenderTexture.active = tempRenderColorTexture;
captureCamera.Render();
colorImage.ReadPixels(new Rect(0, 0, colorImage.width, colorImage.height), 0, 0);
colorImage.Apply();
return colorImage.GetRawTextureData();
}
private void OnDestroy()
{
socket.Dispose();
NetMQConfig.Cleanup();
}
}
In both Python and C# scripts, we created an ZMQ publisher socket that sends a random frames with 400 × 400 × 3 (480000) channels, as if it is a video stream that has a height of 400, width of 400, and 3 color channels. The publisher sends 15 frames per second. The Python script sends random images while the Unity script sends images from a Unity camera.
Important
A ZMQ frame must have data in the following order: topic, timestamp, data. The topic must be a string, and the timestamp must be a 8-byte (64-bit) double or 4-byte (32-bit) float. The data can be one of the supported types listed here.
Now return to PhysioLabXR (download the App here if you haven’t already). In the Add Stream line edit,
type in the name of the stream you created in the script (my_stream_name in the example above).
Select ZMQ as the stream type in the dropdown just right where you put in the stream name.
After ZMQ is selected, the port number line edit will show up. Type in the port number you used in the script (5557 in the example above).
Click on the Add button. A stream widget will be added to the main window. Unlike LSL stream, ZMQ stream will not have
icon on the bottom. This is because ZMQ does not have a mechanism to check if the stream is available on the network.
You will be prompted to auto-set the number of channels to what’s being streamed from your script above. This is because
the default number of channels is 1 for newly added stream widget. Click on Yes to auto-set the number of channels.
Because the number of channels we set is 12288, greater than the maximum number of channels that can be plotted in a line chart.
It will automatically switch to image plot (explore other visualization method).
You need to set the height and width and other image meta info in its to see the image.
Click on ... button
to open the plot settings. Set the width and height to 64, and Image to “rgb”.
Return to the plot widget, move your cursor to the lower left of the plot, click the [A] button that shows up to have the image auto-scale to fit the window.
Note
Check this page for how to choose ZMQ port so that it does not conflict with other applications.
You can create your own data source using the Scripting Interface in PhysiolabXR. This section in Scripting gives detailed examples on how to write your own data source using the third party device driver API and the PhysiolabXR scripting interface.
Video and audio input devices recognize by your OS are automatically detected and can be used as data sources.
Video Devices¶
PhysioLabXR automatically detects the video input devices
connected to your computer. Their name will be listed in the Add Stream dropdown as Camera <camera id>.
To add an video input stream:
Click on the drop down of Add Stream and select the video device you want to add.
Click on Add button.
Audio Input Devices¶
Similar to the the video input devices, PhysioLabXR automatically detects the audio input devices
connected to your computer. Their name will be listed in the Add Stream dropdown.
To add an audio input stream:
Click on the drop down of Add Stream and select the audio device you want to add.
Once selected, a few new options settings will show up on the right of the dropdown.
Set the sampling rate (default: 8192), frame/buffer (default: 128), and data type (default: paInt16) of the audio device.
Click on Add button.
Note
Please refer to the PyAudio documentation for more information about parameters for audio interface.
Screen Capture¶
Similar to the the video input devices, PhysioLabXR supports streaming screen capture. The name of the screen capture stream will be monitor 0 as default.
To add an video input stream:
Click on the drop down of Add Stream and select
monitor 0.Click on Add button.
Refresh the list of devices¶
If you have connected a new device, you can refresh the list of devices by going to File > Settings > Video Device,
and click Reload Video Devices. Similarly, you can refresh the list of audio devices by going to
File > Settings > Audio Device, and click Reload Audio Devices.
If your device is recognized by the OS, but not by PhysioLabXR and the data is not streamed correctly. Please submit an issue here.