Differences with pylsl๐
Faster chunk pull๐
Arguably the most important difference, pulling a chunk of numerical data with
pull_chunk()
is much faster than with its
pylsl counterpart. By default, pylsl loads the
retrieved samples one by one in a list of list, here.
# return results (note: could offer a more efficient format in the
# future, e.g., a numpy array)
num_samples = num_elements / num_channels
if dest_obj is None:
samples = [
[data_buff[s * num_channels + c] for c in range(num_channels)]
for s in range(int(num_samples))
]
if self.channel_format == cf_string:
samples = [[v.decode("utf-8") for v in s] for s in samples]
free_char_p_array_memory(data_buff, max_values)
The creation of the variable samples
is expensive and is performed in linear time
O(n)
, scaling with the number of values. Instead, numpy
can be used to pull
the entire buffer at once with numpy.frombuffer()
.
samples = np.frombuffer(
data_buffer, dtype=self._dtype)[:n_samples_data].reshape(-1, self._n_channels
)
Now, samples
is created in constant time O(1)
. The performance gain varies
depending on the number of values pulled, for instance retrieving 1024 samples with
65 channels in double precision (float64
) takes:
4.33 ms ยฑ 37.5 ยตs with
pylsl
(default behavior)268 ns ยฑ 0.357 ns with
mne_lsl.lsl
Note that pylsl
pulling function support a dest_obj
argument described as:
A Python object that supports the buffer interface.
If this is provided then the dest_obj will be updated in place and the samples list
returned by this method will be empty. It is up to the caller to trim the buffer to
the appropriate number of samples. A numpy buffer must be order='C'.
If a ndarray
is used as dest_obj
, the memory re-allocation step
described abvove is skipped, yielding similar performance to mne_lsl.lsl
. For the
same 1024 samples with 65 channels in double precision (float64
), the pull operation
takes:
471 ns ยฑ 1.7 ns with
pylsl
(withdest_obj
argument asndarray
)
Note that this performance improvement is absent for string
based streams. Follow
#225 for more information.
Convenience methods๐
A StreamInfo
has several convenience methods to retrieve and set
channel attributes: names, types, units.
Those methods eliminate the need to interact with the XMLElement
underlying tree,
present in the mne_lsl.lsl.StreamInfo.desc
property. The description can even
be set or retrieved directly from a Info
object with
set_channel_info()
and
get_channel_info()
.
Improve arguments๐
The arguments of a StreamInfo
, StreamInlet
,
StreamOutlet
support a wider variety of types. For instance:
dtype
, which correspond to thechannel_format
in pylsl, can be provided as a string or as a supportednumpy.dtype
, e.g.np.int8
.processing_flags
can be provided as strings instead of the underlying integer mapping.
Overall, the arguments are checked in mne_lsl.lsl
. Any type or value mistake will
raise an helpful error message.
Unique resolve function๐
pylsl has several stream resolving functions:
resolve_streams
which resolves all streams on the network.resolve_byprop
which resolves all streams with a specific value for a given property.resolve_bypred
which resolves all streams with a given predicate.
mne_lsl.lsl.resolve_streams()
simplifies stream resolution with a unique function
with similar functionalities.