Skip to content

Commit

Permalink
Deploying to gh-pages from @ bad416f 🚀
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] committed Mar 18, 2024
0 parents commit 0389ca3
Show file tree
Hide file tree
Showing 675 changed files with 118,260 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# macOS
.DS_Store
Empty file added .nojekyll
Empty file.
4 changes: 4 additions & 0 deletions 1.0.0/.buildinfo
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 2fafc515f0d76a5b52e482a177270718
tags: 645f666f9bcd5a90fca523b33c5a78b7
175 changes: 175 additions & 0 deletions 1.0.0/_downloads/2c6997f9ff66fdd348ff4d52cfa99b81/00_stream.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n# Introduction to the Stream API\n\n.. include:: ./../../links.inc\n\nAn LSL stream can be consider as a continuous recording, with an unknown length and with\nonly access to the current and past samples. LSL streams can be separate in 2\ncategories:\n\n* Streams with a **regular** sampling rate, which can be considered as a\n :class:`~mne.io.Raw` continuous recording.\n* Streams with an **irregular** sampling rate, which can be considered as spontaneous\n events.\n\nBoth types can be managed through a ``Stream`` object, which represents a\nsingle LSL stream with its buffer containing the current and past samples. The buffer\nsize is specified at instantiation through the ``bufsize`` argument.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Internal ringbuffer\n\nOnce the :class:`~mne_lsl.stream.StreamLSL` object is connected to an LSL Stream, it\nautomatically updates an internal ringbuffer with newly available samples. A\nringbuffer, also called circular buffer, is a data structure that uses a single\nfixed-size buffer as if it were connected and to end.\n\n<img src=\"file://../../_static/tutorials/circular-buffer-light.png\" align=\"center\" class=\"only-light\">\n\n<img src=\"file://../../_static/tutorials/circular-buffer-dark.png\" align=\"center\" class=\"only-dark\">\n\nTypically, a ring buffer has 2 pointers:\n\n* The \"head\" pointer, also called \"start\" or \"read\", which corresponds to the next\n data block to read.\n* The \"tail\" pointer, also called \"end\" or \"write\", which corresponds to the next\n data block that will be overwritten with new data.\n\nWith a `~mne_lsl.stream.StreamLSL`, the pointers are hidden and the head pointer is\nalways updated to the last received sample.\n\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Connect to a Stream\n\nConnecting to an LSL Stream is a 2 step operation. First, create a\n:class:`~mne_lsl.stream.StreamLSL` with the desired buffer size and the desired stream\nattributes, ``name``, ``stype``, ``source_id``. Second, connect to the stream which\nmatches the requested stream attributes with :meth:`mne_lsl.stream.StreamLSL.connect`.\n\n<div class=\"alert alert-info\"><h4>Note</h4><p>For this tutorial purposes, a mock LSL stream is created using a\n :class:`~mne_lsl.player.PlayerLSL`. See\n `sphx_glr_generated_tutorials_10_player.py` for additional information on\n mock LSL streams.</p></div>\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import time\n\nfrom matplotlib import pyplot as plt\n\nfrom mne_lsl.datasets import sample\nfrom mne_lsl.lsl import local_clock\nfrom mne_lsl.player import PlayerLSL as Player\nfrom mne_lsl.stream import StreamLSL as Stream\n\nfname = sample.data_path() / \"sample-ant-raw.fif\"\nplayer = Player(fname)\nplayer.start()\nstream = Stream(bufsize=5) # 5 seconds of buffer\nstream.connect(acquisition_delay=0.2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Stream information\n\nSimilar to a :class:`~mne.io.Raw` recording and to most [MNE](mne stable_) objects,\na :class:`~mne_lsl.stream.StreamLSL` has an ``.info`` attribute containing the channel\nnames, types and units.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"stream.info"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Depending on the LSL Stream source, the `~mne_lsl.stream.StreamLSL` may or may not be\nable to correctly read the channel names, types and units.\n\n* If the channel names are not readable or present, numbers will be used.\n* If the channel types are not readable or present, the stream type or ``'misc'`` will\n be used.\n* If the channel units are not readable or present, SI units will be used.\n\nOnce connected to a Stream, you can change the channel names, types and units to your\nliking with :meth:`mne_lsl.stream.StreamLSL.rename_channels`,\n:meth:`mne_lsl.stream.StreamLSL.set_channel_types` and\n:meth:`mne_lsl.stream.StreamLSL.set_channel_units`. See\n`sphx_glr_generated_tutorials_20_stream_meas_info.py` for additional information.\n\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Channel selection\n\nChannels can be selected with :meth:`mne_lsl.stream.StreamLSL.pick` or with\n:meth:`mne_lsl.stream.StreamLSL.drop_channels`. Selection is definitive, it is not\npossible to restore channels removed until the :class:`~mne_lsl.stream.StreamLSL` is\ndisconnected and reconnected to its source.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"stream.pick([\"Fz\", \"Cz\", \"Oz\"])\nstream.info"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Query the buffer\n\nThe ringbuffer can be queried for the last ``N`` samples with\n:meth:`mne_lsl.stream.StreamLSL.get_data`. The argument ``winsize`` controls the\namount of samples returned, and the property\n:py:attr:`mne_lsl.stream.StreamLSL.n_new_samples` contains the amount of new samples\nbuffered between 2 queries.\n\n<div class=\"alert alert-info\"><h4>Note</h4><p>If the number of new samples between 2 queries is superior to the number of\n samples that can be hold in the buffer\n :py:attr:`mne_lsl.stream.StreamLSL.n_buffer`, the buffer is overwritten with some\n samples \"lost\" or discarded without any prior notice or error raised.</p></div>\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"print(f\"Number of new samples: {stream.n_new_samples}\")\ndata, ts = stream.get_data()\ntime.sleep(0.5)\nprint(f\"Number of new samples: {stream.n_new_samples}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
":meth:`mne_lsl.stream.StreamLSL.get_data` returns 2 variables, ``data`` which contains\nthe ``(n_channels, n_samples)`` data array and ``ts`` (or ``timestamps``) which\ncontains the ``(n_samples,)`` timestamp array, in LSL time.\n\n<div class=\"alert alert-info\"><h4>Note</h4><p>LSL timestamps are not always regular. They can be jittered depending on the source\n and on the delay between the source and the client. Processing flags can be\n provided to improve the timestamp precision when connecting to a stream with\n :meth:`mne_lsl.stream.StreamLSL.connect`. See\n `sphx_glr_generated_tutorials_30_timestamps.py` for additional information.</p></div>\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"t0 = local_clock()\nf, ax = plt.subplots(3, 1, sharex=True, constrained_layout=True)\nfor _ in range(3):\n # figure how many new samples are available, in seconds\n winsize = stream.n_new_samples / stream.info[\"sfreq\"]\n # retrieve and plot data\n data, ts = stream.get_data(winsize)\n for k, data_channel in enumerate(data):\n ax[k].plot(ts - t0, data_channel)\n time.sleep(0.5)\nfor k, ch in enumerate(stream.ch_names):\n ax[k].set_title(f\"EEG {ch}\")\nax[-1].set_xlabel(\"Timestamp (LSL time)\")\nplt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In the previous figure, the timestamps are corrected by ``t0``, which correspond to\nthe time at which the first loop was executed. Note that the samples in blue span\nnegative time values. Indeed, a 0.5 second sleep was added in the previous code cell\nafter the last :meth:`mne_lsl.stream.StreamLSL.get_data` call. Thus, ``t0`` is created\n0.5 seconds after the last reset of :py:attr:`mne_lsl.stream.StreamLSL.n_new_samples`\nand the samples pulled with the first :meth:`mne_lsl.stream.StreamLSL.get_data`\ncorrespond to past samples.\n\nNote also the varying number of samples in each of the 3 data query separated by\n0.5 seconds. When connecting to a Stream with\n:meth:`mne_lsl.stream.StreamLSL.connect`, an ``acquisition_delay`` is defined. It\ncorresponds to the delay between 2 updates of the ringbuffer, 200 ms in this example.\nThus, with a 500 ms sleep in this example, the number of samples updated in the\nringbuffer will vary every 2 iterations.\n\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Apply processing to the buffer\n\nTODO: add_reference_channels, set_eeg_reference, filter\n\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Record a stream\n\nTODO\n\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Free resources\nWhen you are done with a :class:`~mne_lsl.player.PlayerLSL` or\n:class:`~mne_lsl.stream.StreamLSL`, don't forget to free the resources they both use\nto continuously mock an LSL stream or receive new data from an LSL stream.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"stream.disconnect()\nplayer.stop()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""
Stream information
==================
.. include:: ./../../links.inc
A :class:`~mne_lsl.stream.StreamLSL` will automatically attempt to interpret the channel
names, types and units during the connection with
:meth:`mne_lsl.stream.StreamLSL.connect`.
However, by definition, an LSL stream does not require any of those information to be
present. Moreover, the channel type and unit are not standardize, and may be define with
different nomenclature depending on the system and the application emitting the LSL
stream. For instance, an EEG channel might be denoted by the type ``'eeg'`` or
``'electroencephalography'``, or something else entirely.
If ``MNE-LSL`` is not able to interpret the information in a stream description, it will
default to:
* numbers instead of channel names if it failed to load the channel names, ``'0'```,
``'1'``, ... as :func:`mne.create_info` does when the argument ``ch_names`` is
provided as a number of channels.
* The stream type (if interpretable) or ``'misc'`` otherwise if it failed to load the
individual channel types.
* SI units (factor 0) if it failed to load the individual channel units.
The stream and channel type supported correspond to the MNE-supported channel types.
"""

# %%
# Inspecting a stream info
# ------------------------
#
# A :class:`~mne_lsl.stream.StreamLSL` measurement information can be inspected with
# similar methods to a :class:`~mne.io.Raw` object:
# :py:attr:`mne_lsl.stream.StreamLSL.info`,
# :py:attr:`mne_lsl.stream.StreamLSL.ch_names`,
# :meth:`mne_lsl.stream.StreamLSL.get_channel_types`,
# :meth:`mne_lsl.stream.StreamLSL.get_channel_units`.
#
# .. note::
#
# For this tutorial purposes, a mock LSL stream is created using a
# :class:`~mne_lsl.player.PlayerLSL`. See
# :ref:`sphx_glr_generated_tutorials_10_player.py` for additional information on
# mock LSL streams.

from mne_lsl.datasets import sample
from mne_lsl.player import PlayerLSL as Player
from mne_lsl.stream import StreamLSL as Stream

fname = sample.data_path() / "sample-ant-aux-raw.fif"
player = Player(fname)
player.start()
stream = Stream(bufsize=5) # 5 seconds of buffer
stream.connect()
stream.info

# %%
# :py:attr:`mne_lsl.stream.StreamLSL.ch_names` and
# :meth:`mne_lsl.stream.StreamLSL.get_channel_types` behave like their
# `MNE <mne stable_>`_ counterpart, but
# :meth:`mne_lsl.stream.StreamLSL.get_channel_units` is unique to ``MNE-LSL``.
# In `MNE <mne stable_>`_, recordings are expected to be provided in SI units, and it is
# up to the end-user to ensure that the underlying data array is abiding.
#
# However, many system do not stream data in SI units. For instance, most EEG amplifiers
# stream data in microvolts. ``MNE-LSL`` implements a 'units' API to handle the difference
# in units between 2 stream of similar sources, e.g. between an EEG stream from a first
# amplifier in microvolts and an EEG stream from a second amplifier in nanovolts.

# look at the 3 channels with the type 'eeg'
ch_types = stream.get_channel_types(picks="eeg")
ch_units = stream.get_channel_units(picks="eeg")
for ch_name, ch_type, ch_unit in zip(stream.ch_names, ch_types, ch_units):
print(f"Channel '{ch_name}' of type '{ch_type}' has the unit '{ch_unit}'.")

# %%
# In our case, the 3 selected channels have the unit
# ``((107 (FIFF_UNIT_V), 0 (FIFF_UNITM_NONE))``. This format contains 2 elements:
#
# * The first element, ``107 (FIFF_UNIT_V)``, gives the unit type/family. In this case,
# ``V`` means that the unit type is ``Volts``. Each sensor type is associated to a
# different unit type, thus to change the first element the sensor type must be set
# with :meth:`mne_lsl.stream.StreamLSL.set_channel_types`.
# * The second element, ``0 (FIFF_UNITM_NONE))``, gives the unit scale (Giga, Kilo,
# micro, ...) in the form of the power of 10 multiplication factor. In this case,
# ``0`` means ``e0``, i.e. ``10**0``.
#
# Thus, the unit stored is ``Volts``, corresponding to the SI unit for
# electrophysiological channels.
#
# Correct a stream info
# ---------------------
#
# If a :py:attr:`mne_lsl.stream.StreamLSL.info` does not contain the correct attributes,
# it should be corrected similarly as for a :class:`~mne.io.Raw` object. In this case:
#
# * the channel ``AUX1`` is a vertical EOG channel.
# * the channel ``AUX2`` is an ECG channel.
# * the channel ``AUX3`` is an horizontal EOG channel.

stream.rename_channels({"AUX1": "vEOG", "AUX2": "ECG", "AUX3": "hEOG"})
stream.set_channel_types({"vEOG": "eog", "hEOG": "eog", "ECG": "ecg"})
stream.info

# %%
# TODO: section about setting the channel units

# %%
# Free resources
# --------------
# When you are done with a :class:`~mne_lsl.player.PlayerLSL` or
# :class:`~mne_lsl.stream.StreamLSL`, don't forget to free the resources they both use
# to continuously mock an LSL stream or receive new data from an LSL stream.

stream.disconnect()
player.stop()
Loading

0 comments on commit 0389ca3

Please sign in to comment.