LSL.jl

A Julia interface to the lab streaming layer library, liblsl
Author samuelpowell
Popularity
3 Stars
Updated Last
2 Years Ago
Started In
October 2019

LSL.jl

Build Status Build Status

A Julia interface to the lab streaming layer library.

Installation & platform support

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.

Usage

LSL.jl provides an interface similar to the official Python bindings, with some changes to ensure the wrapper follows idiomatic Julia.

Specifying a stream

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.

Creating a stream outlet, and pushing data

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.

Finding streams on the network

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)

Creating a stream inlet, and reading data

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.

Adding extended metadata to stream information

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)

Low level library access

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 StreamInfos, StreamOutlets, and StreamInlets 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.

Required Packages

Used By Packages

No packages found.