.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "generated/tutorials/20_player_annotations.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_generated_tutorials_20_player_annotations.py: .. _tut-player-annotations: Player with annotations ======================= .. include:: ./../../links.inc :class:`~mne.Annotations` from a :class:`~mne.io.Raw` object can be streamed as an event stream by :class:`~mne_lsl.player.PlayerLSL`. The stream is irregularly sampled and of type ``'annotations'``. A :class:`~mne.Annotations` contain 3 information: - the onset of the annotation - the duration of the annotation - the description of the annotation The :class:`~mne_lsl.player.PlayerLSL` supports two encodings for the annotation stream, selected with the ``annotations_encoding`` argument. With the default ``annotations_encoding="one-hot"``, all 3 information are streamed on a numerical stream by encoding the duration on a one-hot representation of the description along the channels. For instance, consider a :class:`~mne.io.Raw` object with 3 different :class:`~mne.Annotations` description: ``'event1'``, ``'event2'``, and ``'event3'``. The event stream will have 3 channels, each corresponding to one of the 3 descriptions. When an annotation is streamed, its duration is encoded as the value on its channel while the other channels remain to zero. .. note:: Annotation with a duration equal to zero are special cased and yield an encoded value of ``-1``. With ``annotations_encoding="string"``, only the description is streamed, on a single ``'string'``-typed channel named ``'description'``. This is convenient when a file contains a large or variable number of unique descriptions, as the number of channels no longer depends on the annotations present. The onset is still conveyed through the sample timestamp, but the duration is not encoded and therefore lost. A ``'string'``-typed stream must be received with a :class:`~mne_lsl.lsl.StreamInlet` directly, as the :class:`~mne_lsl.stream.StreamLSL` and :class:`~mne_lsl.stream.EpochsStream` classes only support numerical streams. .. GENERATED FROM PYTHON SOURCE LINES 44-66 .. code-block:: Python import uuid import matplotlib.patches as mpatches import numpy as np from matplotlib import pyplot as plt from mne import Annotations, create_info from mne.io import RawArray from mne.viz import set_browser_backend from mne_lsl.lsl import StreamInlet, resolve_streams from mne_lsl.player import PlayerLSL from mne_lsl.stream import StreamLSL annotations = Annotations( onset=[1, 2, 3], duration=[0.1, 0.2, 0.3], description=["event1", "event2", "event3"], ) annotations .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 68-82 With the 3 annotations above, the event stream will stream the following samples: - at time ``1``, the annotation ``'event1'`` is pushed. The sample push is ``array([[0.1, 0, 0]])``, of shape (1, 3). - at time ``2``, the annotation ``'event2'`` is pushed. The sample push is ``array([[0, 0.2, 0]])``, of shape (1, 3). - at time ``3``, the annotation ``'event3'`` is pushed. The sample push is ``array([[0, 0, 0.3]])``, of shape (1, 3). If more than one annotations are present in the chunk currently pushed, then a chunk is pushed. For instance, if the annotations at time ``2`` and ``3`` are pushed at the same time, then the chunk push is ``array([[0., 0.2., 0.], [0., 0., 0.3]])``, of shape (2, 3). .. GENERATED FROM PYTHON SOURCE LINES 84-89 Example on mock signal ---------------------- Let's create a mock :class:`~mne.io.Raw` object with annotations and stream both the signal and the annotations. .. GENERATED FROM PYTHON SOURCE LINES 89-101 .. code-block:: Python data = np.zeros((1, 1000)) # 1 channel, 1000 samples data[0, 100:200] = 1 data[0, 500:700] = 2 info = create_info(["signal"], 1000, "misc") raw = RawArray(data, info) annotations = Annotations(onset=[0.1, 0.5], duration=[0.1, 0.2], description=["1", "2"]) raw.set_annotations(annotations) set_browser_backend("matplotlib") # easier to plot with matplotlib in a documentation raw.plot(scalings=dict(misc=2), show_scrollbars=False, show_scalebars=False) plt.show() .. image-sg:: /generated/tutorials/images/sphx_glr_20_player_annotations_001.png :alt: 20 player annotations :srcset: /generated/tutorials/images/sphx_glr_20_player_annotations_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 102-115 Now that we have the :class:`~mne.io.Raw` object, we can stream it with a :class:`~mne_lsl.player.PlayerLSL` object. .. note:: Note that forcing ``annotations=True`` is not necessary since the :class:`~mne_lsl.player.PlayerLSL` will automatically stream annotations if they are present in the :class:`~mne.io.Raw` object. .. note:: A ``chunk_size`` of 1 is needed here or the timestamps ``ts`` from the signal and annotations streams are not reliable enough. .. GENERATED FROM PYTHON SOURCE LINES 115-125 .. code-block:: Python source_id = uuid.uuid4().hex player = PlayerLSL( raw, chunk_size=1, name="tutorial-annots", source_id=source_id, annotations=True, ).start() .. GENERATED FROM PYTHON SOURCE LINES 126-127 We can now acquire both streams with 2 :class:`~mne_lsl.stream.StreamLSL` objects. .. GENERATED FROM PYTHON SOURCE LINES 127-132 .. code-block:: Python stream = StreamLSL(2, name="tutorial-annots", source_id=source_id) stream.connect(acquisition_delay=0.1, processing_flags="all") stream.info .. raw:: html
General
MNE object type Info
Measurement date Unknown
Participant Unknown
Experimenter Unknown
Acquisition
Sampling frequency 1000.00 Hz
Channels
misc
Head & sensor digitization Not available
Filters
Highpass 0.00 Hz
Lowpass 500.00 Hz


.. GENERATED FROM PYTHON SOURCE LINES 133-138 .. code-block:: Python stream_annotations = StreamLSL(2, stype="annotations", source_id=source_id) stream_annotations.connect(acquisition_delay=0.1, processing_flags="all") stream_annotations.info .. raw:: html
General
MNE object type Info
Measurement date Unknown
Participant Unknown
Experimenter Unknown
Acquisition
Sampling frequency 0.00 Hz
Channels
misc
Head & sensor digitization Not available
Filters
Highpass 0.00 Hz
Lowpass 0.00 Hz


.. GENERATED FROM PYTHON SOURCE LINES 139-141 We can now acquire new samples from both streams and create a matplotlib figure to plot the signal and the annotations in real-time. .. GENERATED FROM PYTHON SOURCE LINES 141-180 .. code-block:: Python if not plt.isinteractive(): plt.ion() fig, ax = plt.subplots() # add legend colors = ["lightcoral", "lightgreen"] patches = [ mpatches.Patch(color=colors[k], label=ch, alpha=0.5) for k, ch in enumerate(stream_annotations.ch_names) ] ax.legend(handles=patches, loc="upper left") plt.show() n = 0 # number of annotations while n <= 10: if stream.n_new_samples == 0: continue data, ts = stream.get_data(winsize=stream.n_new_samples / stream.info["sfreq"]) ax.plot(ts, data.squeeze(), color="teal") if stream_annotations.n_new_samples != 0: data_annotations, ts_annotations = stream_annotations.get_data( winsize=stream_annotations.n_new_samples ) for sample, time in zip(data_annotations.T, ts_annotations, strict=True): k = np.where(sample != 0)[0][0] # find the annotation ax.axvspan( time, time + sample[k], label=stream_annotations.ch_names[k], color=colors[k], alpha=0.5, ) n += 1 fig.canvas.draw() fig.canvas.flush_events() .. image-sg:: /generated/tutorials/images/sphx_glr_20_player_annotations_002.png :alt: 20 player annotations :srcset: /generated/tutorials/images/sphx_glr_20_player_annotations_002.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 181-188 Free resources -------------- When you are done with a :class:`~mne_lsl.player.PlayerLSL`, a :class:`~mne_lsl.stream.StreamLSL` or a :class:`~mne_lsl.stream.EpochsStream` don't forget to free the resources they use to continuously mock an LSL stream or receive new data from an LSL stream. .. GENERATED FROM PYTHON SOURCE LINES 188-191 .. code-block:: Python stream.disconnect() .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 192-195 .. code-block:: Python stream_annotations.disconnect() .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 196-199 .. code-block:: Python player.stop() .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 200-208 Streaming annotations as strings -------------------------------- The one-hot encoding above conveys the onset, duration and description, but it creates one channel per unique description. With many unique descriptions, e.g. a file with hundreds of distinct markers, this yields a large and file-dependent number of channels. If the duration is not needed, ``annotations_encoding="string"`` streams the descriptions on a single ``'string'``-typed channel named ``'description'`` instead. .. GENERATED FROM PYTHON SOURCE LINES 208-218 .. code-block:: Python source_id = uuid.uuid4().hex player = PlayerLSL( raw, chunk_size=1, name="tutorial-annots-str", source_id=source_id, annotations_encoding="string", ).start() .. GENERATED FROM PYTHON SOURCE LINES 219-223 A ``'string'``-typed stream can not be received with a :class:`~mne_lsl.stream.StreamLSL`, which only supports numerical streams. We resolve the annotation stream and connect to it with a :class:`~mne_lsl.lsl.StreamInlet` instead. .. GENERATED FROM PYTHON SOURCE LINES 223-232 .. code-block:: Python sinfo = [ stream for stream in resolve_streams(timeout=2) if stream.stype == "annotations" and stream.source_id == source_id ][0] inlet = StreamInlet(sinfo) inlet.open_stream(timeout=5) .. GENERATED FROM PYTHON SOURCE LINES 233-235 Each pulled sample is a list with a single element: the annotation description. The associated timestamp still encodes the onset, while the duration is lost. .. GENERATED FROM PYTHON SOURCE LINES 235-244 .. code-block:: Python descriptions = [] for _ in range(20): data, ts = inlet.pull_chunk(timeout=1) descriptions.extend(sample[0] for sample in data) if 4 <= len(descriptions): break descriptions .. rst-class:: sphx-glr-script-out .. code-block:: none ['1', '2', '1', '2'] .. GENERATED FROM PYTHON SOURCE LINES 245-250 Free resources. Similarly, when you are done with a :class:`~mne_lsl.lsl.StreamInlet`, don't forget to free the resources they use to continuously mock an LSL stream or receive new data from an LSL stream. .. GENERATED FROM PYTHON SOURCE LINES 250-254 .. code-block:: Python inlet.close_stream() del inlet player.stop() .. rst-class:: sphx-glr-script-out .. code-block:: none .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 14.505 seconds) **Estimated memory usage:** 197 MB .. _sphx_glr_download_generated_tutorials_20_player_annotations.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: 20_player_annotations.ipynb <20_player_annotations.ipynb>` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: 20_player_annotations.py <20_player_annotations.py>` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: 20_player_annotations.zip <20_player_annotations.zip>` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_