{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n\n# Annotating continuous data\n\nThis tutorial describes adding annotations to a `~mne.io.Raw` object,\nand how annotations are used in later stages of data processing.\n\nAs usual we'll start by importing the modules we need, loading some\n`example data `, and (since we won't actually analyze the\nraw data in this tutorial) cropping the `~mne.io.Raw` object to just 60\nseconds before loading it into RAM to save memory:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Authors: The MNE-Python contributors.\n# License: BSD-3-Clause\n# Copyright the MNE-Python contributors." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import os\nfrom datetime import timedelta\n\nimport mne\n\nsample_data_folder = mne.datasets.sample.data_path()\nsample_data_raw_file = os.path.join(\n sample_data_folder, \"MEG\", \"sample\", \"sample_audvis_raw.fif\"\n)\nraw = mne.io.read_raw_fif(sample_data_raw_file, verbose=False)\nraw.crop(tmax=60).load_data()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`~mne.Annotations` in MNE-Python are a way of storing short strings of\ninformation about temporal spans of a `~mne.io.Raw` object. Below the\nsurface, `~mne.Annotations` are `list-like ` objects,\nwhere each element comprises three pieces of information: an ``onset`` time\n(in seconds), a ``duration`` (also in seconds), and a ``description`` (a text\nstring). Additionally, the `~mne.Annotations` object itself also keeps\ntrack of ``orig_time``, which is a `POSIX timestamp`_ denoting a real-world\ntime relative to which the annotation onsets should be interpreted.\n\n\n\n## Creating annotations programmatically\n\nIf you know in advance what spans of the `~mne.io.Raw` object you want\nto annotate, `~mne.Annotations` can be created programmatically, and\nyou can even pass lists or arrays to the `~mne.Annotations`\nconstructor to annotate multiple spans at once:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "my_annot = mne.Annotations(\n onset=[3, 5, 7], # in seconds\n duration=[1, 0.5, 0.25], # in seconds, too\n description=[\"AAA\", \"BBB\", \"CCC\"],\n)\nprint(my_annot)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that ``orig_time`` is ``None``, because we haven't specified it. In\nthose cases, when you add the annotations to a `~mne.io.Raw` object,\nit is assumed that the ``orig_time`` matches the time of the first sample of\nthe recording, so ``orig_time`` will be set to match the recording\nmeasurement date (``raw.info['meas_date']``).\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "raw.set_annotations(my_annot)\nprint(raw.annotations)\n\n# convert meas_date (a tuple of seconds, microseconds) into a float:\nmeas_date = raw.info[\"meas_date\"]\norig_time = raw.annotations.orig_time\nprint(meas_date == orig_time)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since the example data comes from a Neuromag system that starts counting\nsample numbers before the recording begins, adding ``my_annot`` to the\n`~mne.io.Raw` object also involved another automatic change: an offset\nequalling the time of the first recorded sample (``raw.first_samp /\nraw.info['sfreq']``) was added to the ``onset`` values of each annotation\n(see `time-as-index` for more info on ``raw.first_samp``):\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "time_of_first_sample = raw.first_samp / raw.info[\"sfreq\"]\nprint(my_annot.onset + time_of_first_sample)\nprint(raw.annotations.onset)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you know that your annotation onsets are relative to some other time, you\ncan set ``orig_time`` before you call :meth:`~mne.io.Raw.set_annotations`,\nand the onset times will get adjusted based on the time difference between\nyour specified ``orig_time`` and ``raw.info['meas_date']``, but without the\nadditional adjustment for ``raw.first_samp``. ``orig_time`` can be specified\nin various ways (see the documentation of `~mne.Annotations` for the\noptions); here we'll use an `ISO 8601`_ formatted string, and set it to be 50\nseconds later than ``raw.info['meas_date']``.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "time_format = \"%Y-%m-%d %H:%M:%S.%f\"\nnew_orig_time = (meas_date + timedelta(seconds=50)).strftime(time_format)\nprint(new_orig_time)\n\nlater_annot = mne.Annotations(\n onset=[3, 5, 7],\n duration=[1, 0.5, 0.25],\n description=[\"DDD\", \"EEE\", \"FFF\"],\n orig_time=new_orig_time,\n)\n\nraw2 = raw.copy().set_annotations(later_annot)\nprint(later_annot.onset)\nprint(raw2.annotations.onset)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

Note

If your annotations fall outside the range of data times in the\n `~mne.io.Raw` object, the annotations outside the data range will\n not be added to ``raw.annotations``, and a warning will be issued.

\n\nNow that your annotations have been added to a `~mne.io.Raw` object,\nyou can see them when you visualize the `~mne.io.Raw` object:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig = raw.plot(start=2, duration=6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The three annotations appear as differently colored rectangles because they\nhave different ``description`` values (which are printed along the top\nedge of the plot area). Notice also that colored spans appear in the small\nscroll bar at the bottom of the plot window, making it easy to quickly view\nwhere in a `~mne.io.Raw` object the annotations are so you can easily\nbrowse through the data to find and examine them.\n\n\n## Annotating Raw objects interactively\n\nAnnotations can also be added to a `~mne.io.Raw` object interactively\nby clicking-and-dragging the mouse in the plot window. To do this, you must\nfirst enter \"annotation mode\" by pressing :kbd:`a` while the plot window is\nfocused; this will bring up the annotation controls:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig = raw.plot(start=2, duration=6)\nfig.fake_keypress(\"a\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The drop-down-menu on the left determines which existing label will be\ncreated by the next click-and-drag operation in the main plot window. New\nannotation descriptions can be added by clicking the :guilabel:`Add\ndescription` button; the new description will be added to the list of\ndescriptions and automatically selected.\nThe following functions relate to which description is currently selected in\nthe drop-down-menu:\nWith :guilabel:`Remove description` you can remove description\nincluding the annotations.\nWith :guilabel:`Edit description` you can edit\nthe description of either only one annotation (the one currently selected)\nor all annotations of a description.\nWith :guilabel:`Set Visible` you can show or hide descriptions.\n\nDuring interactive annotation it is also possible to adjust the start and end\ntimes of existing annotations, by clicking-and-dragging on the left or right\nedges of the highlighting rectangle corresponding to that annotation. When\nan annotation is selected (the background of the label at the bottom changes\nto darker) the values for start and stop are visible in two spinboxes and\ncan also be edited there.\n\n

Warning

Calling :meth:`~mne.io.Raw.set_annotations` **replaces** any annotations\n currently stored in the `~mne.io.Raw` object, so be careful when\n working with annotations that were created interactively (you could lose\n a lot of work if you accidentally overwrite your interactive\n annotations). A good safeguard is to run\n ``interactive_annot = raw.annotations`` after you finish an interactive\n annotation session, so that the annotations are stored in a separate\n variable outside the `~mne.io.Raw` object.

\n\n\n## How annotations affect preprocessing and analysis\n\nYou may have noticed that the description for new labels in the annotation\ncontrols window defaults to ``BAD_``. The reason for this is that annotation\nis often used to mark bad temporal spans of data (such as movement artifacts\nor environmental interference that cannot be removed in other ways such as\n`projection ` or filtering). Several\nMNE-Python operations\nare \"annotation aware\" and will avoid using data that is annotated with a\ndescription that begins with \"bad\" or \"BAD\"; such operations typically have a\nboolean ``reject_by_annotation`` parameter. Examples of such operations are\nindependent components analysis (`mne.preprocessing.ICA`), functions\nfor finding heartbeat and blink artifacts\n(:func:`~mne.preprocessing.find_ecg_events`,\n:func:`~mne.preprocessing.find_eog_events`), and creation of epoched data\nfrom continuous data (`mne.Epochs`). See `tut-reject-data-spans`\nfor details.\n\n\n## Operations on Annotations objects\n\n`~mne.Annotations` objects can be combined by simply adding them with\nthe ``+`` operator, as long as they share the same ``orig_time``:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "new_annot = mne.Annotations(onset=3.75, duration=0.75, description=\"AAA\")\nraw.set_annotations(my_annot + new_annot)\nraw.plot(start=2, duration=6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that it is possible to create overlapping annotations, even when they\nshare the same description. This is *not* possible when annotating\ninteractively; click-and-dragging to create a new annotation that overlaps\nwith an existing annotation with the same description will cause the old and\nnew annotations to be merged.\n\nIndividual annotations can be accessed by indexing an\n`~mne.Annotations` object, and subsets of the annotations can be\nachieved by either slicing or indexing with a list, tuple, or array of\nindices:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(raw.annotations[0]) # just the first annotation\nprint(raw.annotations[:2]) # the first two annotations\nprint(raw.annotations[(3, 2)]) # the fourth and third annotations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also iterate over the annotations within an `~mne.Annotations`\nobject:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "for ann in raw.annotations:\n descr = ann[\"description\"]\n start = ann[\"onset\"]\n end = ann[\"onset\"] + ann[\"duration\"]\n print(f\"'{descr}' goes from {start} to {end}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that iterating, indexing and slicing `~mne.Annotations` all\nreturn a copy, so changes to an indexed, sliced, or iterated element will not\nmodify the original `~mne.Annotations` object.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# later_annot WILL be changed, because we're modifying the first element of\n# later_annot.onset directly:\nlater_annot.onset[0] = 99\n\n# later_annot WILL NOT be changed, because later_annot[0] returns a copy\n# before the 'onset' field is changed:\nlater_annot[0][\"onset\"] = 77\n\nprint(later_annot[0][\"onset\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Reading and writing Annotations to/from a file\n\n`~mne.Annotations` objects have a :meth:`~mne.Annotations.save` method\nwhich can write :file:`.fif`, :file:`.csv`, and :file:`.txt` formats (the\nformat to write is inferred from the file extension in the filename you\nprovide). Be aware that the format of the onset information that is written\nto the file depends on the file extension. While :file:`.csv` files store the\nonset as timestamps, :file:`.txt` files write floats (in seconds). There is a\ncorresponding :func:`~mne.read_annotations` function to load them from disk:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "raw.annotations.save(\"saved-annotations.csv\", overwrite=True)\nannot_from_file = mne.read_annotations(\"saved-annotations.csv\")\nprint(annot_from_file)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ".. LINKS\n\n\n" ] } ], "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.12.2" } }, "nbformat": 4, "nbformat_minor": 0 }