Monitoring LoopIT output with Lab Streaming Layer (LSL)
2026-06-05
Introduction
The LoopIT publishes its live data over Lab Streaming Layer (LSL), the de-facto standard for time-synchronised data streams in neuroscience. Any LSL client on the same network can discover the device’s streams and pull samples, without touching the control interface. This path is read-only: it is for monitoring and recording, not control.
This document explains what the device advertises, how to find and read it, and how the streams relate to the fields you mark for streaming in a script. It assumes you know what a TCP/IP network is and can run a small Python program, but not that you have used LSL before.
What gets streamed
The device exposes its data as one or more LSL outlets. There is one outlet per internal data source that has fields marked for streaming — for example the real-time scripting engine’s outputs are published as one outlet, and the event-driven engine’s as another. Within an outlet, each marked field becomes one channel.
A field is marked for streaming by the emit flag in a
script’s interface block. If a script declares
1w emit unsigned ticks;, then ticks appears as
a channel on that engine’s outlet. Fields without emit are
read on request over the parameter server but are not streamed.
Stream metadata
When you resolve the device’s streams, each outlet describes itself with the following metadata. Knowing the shape helps you pick the right stream and interpret its channels.
| Property | Value |
|---|---|
| Stream name | the internal source’s identifier (for example
emit/ralee for the real-time engine, emit/evee
for the event-driven engine) |
| Type | various |
| Source id | the device host name, then ::, then the source’s path
and version (stable across reconnections) |
| Manufacturer | neuroconn |
| Sample rate | declared as 1000 Hz |
| Sample format | 64-bit floating point (double), which carries the
device’s integer values exactly |
Each channel additionally carries a label, a unit, a type, a bit
width and a count, taken straight from the field’s definition in the
interface. The channel label has the form
module.index.field (for example ral.0.ticks),
so you can map a channel back to the exact field that produced it.
Because channels mirror a device’s specific configuration and the script currently running, the exact stream and channel set is device- and script-dependent. Discover it at runtime rather than assuming a fixed layout. There is no separate event or marker stream; everything is a continuous channel, and discrete events appear as a channel whose value changes (for example a flag going from 0 to 1).
Finding and reading a stream
LSL clients resolve streams by a property such as
name or type, then open an inlet to pull samples.
Resolve rather than hard-code, so your client keeps working when the
configuration changes. A short Python example using
pylsl:
from pylsl import resolve_byprop, StreamInlet
# find the real-time engine's outlet by name
streams = resolve_byprop("name", "emit/ralee", timeout=5)
inlet = StreamInlet(streams[0])
# read the channel labels from the stream's metadata
info = inlet.info()
ch = info.desc().child("channels").child("channel")
while not ch.empty():
print(ch.child_value("label"), ch.child_value("unit"))
ch = ch.next_sibling()
# pull samples
while True:
sample, timestamp = inlet.pull_sample()
print(timestamp, sample)
You can resolve by type (various) if you
want every LoopIT-style stream, or by name for a specific
engine. Reading the channel metadata first, as above, tells you what
each value in sample means.
The official LSL documentation is the best reference for client libraries in other languages and for the resolve operations:
- Resolving streams: https://labstreaminglayer.readthedocs.io/info/user_guide.html#resolve-operations
- Network connectivity (useful when streams are not discovered): https://labstreaminglayer.readthedocs.io/info/network-connectivity.html
Troubleshooting
- No streams found. LSL discovery uses multicast, which some networks restrict. Confirm the client and device are on the same subnet and read the LSL network-connectivity guide linked above; you may need to configure known peers.
- A field is missing from the stream. Only fields
with the
emitflag are streamed. Check the script’s interface block, and confirm the script that declares the field is the one currently deployed. - Channel order looks different than expected. Do not
rely on position; read the channel
labelmetadata and match by name. - Values look unscaled. Channel values are the
device’s integers carried as doubles; use the channel
unitmetadata to convert to physical units. Some analysis results are pre-scaled (for example a moving average returned ×1000) — the field name and unit indicate this.
Frequently asked questions
What streams and channels will I see? It depends on the device’s configuration and the script currently running. Resolve the streams at runtime and read their channel metadata; do not hard-code names. See What gets streamed and Stream metadata.
How do I monitor a value computed by my script? Mark
the field emit in the script’s interface block; it then
appears as a channel, labelled ral.0.<field>, on the
engine’s outlet. See What gets
streamed.
Can I control the device over LSL? No. LSL is read-only monitoring. Use the JSON parameter server for control.
References
- Lab Streaming Layer documentation: https://labstreaminglayer.readthedocs.io/.