A Julia interface to the lab streaming layer library.
LSL is a registered package. Install using the package manager:
]add LSL
Platform | Architecture | Notes |
---|---|---|
Linux (x86) | 32-bit and 64-bit | CI active |
MacOS | 64-bit | CI active |
Windows | 64-bit | CI active |
For Julia > 1.3, the library is cross-compiled for Windows (x64), Linux (x86, x64, ARMv7, ARMv8), MacOS, and FreeBSD using the BinaryBuilder package, with CI builds undertaken on the Yggdrasil platform. Binaries are packaged and supplied through the JuliaBinaryWrappers organisation. Platforms for which the library are built, but not listed as supported, are untested or known to fail.
LSL.jl provides an interface similar to the official Python bindings, with some changes to ensure the wrapper follows idiomatic Julia.
A new stream is specified by building a SteamInfo
structure:
info = StreamInfo(name = "streamname",
type = "streamtype",
channel_count = 16,
channel_format = Float64,
source_id = "streamuuid")
A stream information structure can be quieried using methods such as name(info)
,
type(info)
, channel_count(info)
, nominal_srate(info)
, channel_format(info)
,
source_id(info)
, version(info)
, created_at(info)
, session_id(info)
, uid(info)
,
hostname(info)
. Get help on these and all other methods provided by the library using
the Julia help system.
To advertise the stream on the network, and allow data to be sent, create a StreamOutlet
structure:
outlet = StreamOutlet(info)
You may push a vector of channel_count(info)
samples of type channel_format(info)
by
using the push_sample
method:
sample = rand(Float64, 16)
push_sample(outlet, sample)
Push a massive chunk of data consisting of many such samples with a matrix of appropriate dimension:
chunk = rand(Float64, 16, 1024)
push_chunk(outlet, sample)
Check if anyone is listening to the outlet by calling have_consumers(info)
, or block
on a connection using wait_for_consumers(info
). Note that the latter function is a blocking
C call, and this will prevent Julia from switching between Tasks if you choose to use this
function in an asynchronous operation. It may be preferable to simple poll the former
function.
Find all streams on the network, waiting two seconds for discovery:
streams = resolve_streams(timeout = 2.0)
This function returns a vector of StreamInfo
structures, each of which can be queried or
read from. Alternatively, you may wish to resolve a stream by property:
streams = resolve_byprop("source_id", "streamname", timeout = 2.0)
Or using a predicate:
streams = resolve_bypred("type=streamtype", timeout = 2.0)
To get some data, given a StreamInfo
structure, create a StreamInlet
structure:
inlet = StreamInlet(streams[1])
You can open_stream(inlet)
, close_stream(inlet)
, set_postprocessing(inlet)
, and check
if samples_available(inlet)
, etc. But probably you're more intersted in getting samples:
sample, timestamp = pull_sample(inlet, timeout = 10.0)
Be careful, the default timeout will wait forever (timeout = LSL.LSL_FOREVER
). For high
performance code you may want to reuse an existing vector:
timestamp = pull_sample!(sample, inlet, timeout = 10.0)
To grab a chunk of data:
chunk, timestamps = pull_chunk(inlet, timeout = 10.0, max_samples = 512)
Since the size of the available chunk is not known until the library returns, a large
allocation (equal to a chunk size of max_samples
) is made by this function, and resized
accordingly. This may not offer the best performance in a hot loop.
Streams can be annotated using structured metadata as described in the XDF format. For example, an EEG recording may employ the meta-data in the associated specification.
info = StreamInfo(name="BioSemi",
type="EEG",
channel_count=8,
nominal_srate=100,
channel_format=Float32,
source_id="sub_ae852")
channels = append_child(desc(info), "channels")
for label in ["C3", "C4", "Cz", "FPz", "POz", "CPz", "O1", "O2"]
ch = append_child(channels, "channel")
append_child_value(ch, "label", label)
append_child_value(ch, "unit", "microvolts")
append_child_value(ch, "type", "EEG")
end
append_child_value(desc(info), "manufacturer", "SCCN")
cap = append_child(desc(info), "cap")
append_child_value(cap, "name", "EasyCap")
append_child_value(cap, "size", "54")
append_child_value(cap, "labelscheme", "10-20")
Full stream metadata can be rendered as XML:
XML(info)
The full C API of liblsl is wrapped by the package, and the functions can be accessed by
their usual names, in the lib
submodule, e.g. LSL.lib.lsl_get_name(info)
. Julia structures
such as StreamInfo
s, StreamOutlet
s, and StreamInlet
s will automatically convert to their
C handle when used as arguments to the C library. Alternatively you may get a pointer by
accesing the .handle
property of each.