LslOut
Publish a frame as an LSL stream, with the labels and rate it arrived with.
input
- Type name
signal:LslOut- Plane
signal- Tags
output- Language
python- Tier
in-process- Bundle
signal- Source
node-bundles/signal/lsl_out.py- Availability
- available
Slots
| Slot | Direction | Type | |
|---|---|---|---|
input | input | ARRAY |
Parameters
lsl
| Name | Type | Default | Range | Doc |
|---|---|---|---|---|
name | string | goofi | The name other programs resolve the stream by. | |
type | string | EEG | The stream's type, such as EEG or Audio. |
common
| Name | Type | Default | Range | Doc |
|---|---|---|---|---|
autotrigger | bool | false | Run on the node's own schedule, instead of waiting for an input frame. Turn this on for sources; leave it off for transforms driven by their input. | |
max_frequency | float | 0 | 0 … 100 | Rate cap for this node, read through `frequency_mode`. 0 means uncapped — the node runs as often as the scheduler and its inputs allow. |
frequency_mode | string | updates_per_second | updates_per_second | seconds_per_update | How to read `max_frequency`: as a rate in Hz (updates per second), or as a period in seconds between updates — convenient for very slow nodes. |
Source
The current source of this node, as it stands in the goofi repository at node-bundles/signal/lsl_out.py.
"""LslOut — publishes a frame as a Lab Streaming Layer stream.
`[C]` pushes one sample, `[C, T]` a chunk. The channel names and the sample rate come from the
frame, so the outlet describes the signal it carries; it is rebuilt when either of those changes,
because an outlet's description is fixed for its life.
"""
import numpy as np
import pylsl
import goofi
class LslOut(goofi.Node):
"""Publish a frame as an LSL stream, with the labels and rate it arrived with."""
TAGS = ["output"]
INPUTS = {"input": goofi.InputSlot(goofi.DataType.ARRAY, required=True)}
PARAMS = {
"lsl": {
"name": goofi.StringParam("goofi", doc="The name other programs resolve the stream by."),
"type": goofi.StringParam("EEG", doc="The stream's type, such as EEG or Audio."),
}
}
def setup(self):
self.outlet = None
self.built = None
def process(self, input):
p = self.params.lsl
raw = np.asarray(input.data, dtype=np.float32)
if raw.ndim not in (1, 2):
raise ValueError(f"needs [C] or [C, T], got {list(raw.shape)}")
x = raw[:, None] if raw.ndim == 1 else raw
sfreq = float(input.meta.get("sfreq") or 0.0)
labels = list(input.meta.get("channels", {}).get("dim0") or [])
wanted = (p.name, p.type, x.shape[0], sfreq, tuple(labels))
if wanted != self.built:
info = pylsl.StreamInfo(p.name, p.type, x.shape[0], sfreq, pylsl.cf_float32, f"goofi-{p.name}")
channels = info.desc().append_child("channels")
for i in range(x.shape[0]):
name = labels[i] if i < len(labels) else f"{p.type} {i + 1}"
channels.append_child("channel").append_child_value("label", str(name))
self.outlet = pylsl.StreamOutlet(info)
self.built = wanted
if raw.ndim == 1:
self.outlet.push_sample(x[:, 0])
else:
self.outlet.push_chunk(x.T.tolist())
return NoneThis reference describes goofi 3.1.0(537cd394), generated from a running instance on 2026-09-06.