{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n\n# Creating MNE-Python data structures from scratch\n\nThis tutorial shows how to create MNE-Python's core data structures using an\nexisting :class:`NumPy array ` of (real or synthetic) data.\n\nWe begin by importing the necessary Python modules:\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 numpy as np\n\nimport mne" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating `~mne.Info` objects\n\n.. admonition:: Info objects\n :class: sidebar note\n\n For full documentation on the `~mne.Info` object, see\n `tut-info-class`.\n\nThe core data structures for continuous (`~mne.io.Raw`), discontinuous\n(`~mne.Epochs`), and averaged (`~mne.Evoked`) data all have an ``info``\nattribute comprising an `mne.Info` object. When reading recorded data using\none of the functions in the ``mne.io`` submodule, `~mne.Info` objects are\ncreated and populated automatically. But if we want to create a\n`~mne.io.Raw`, `~mne.Epochs`, or `~mne.Evoked` object from scratch, we need\nto create an appropriate `~mne.Info` object as well. The easiest way to do\nthis is with the `mne.create_info` function to initialize the required info\nfields. Additional fields can be assigned later as one would with a regular\n:class:`dictionary `.\n\nTo initialize a minimal `~mne.Info` object requires a list of channel names,\nand the sampling frequency. As a convenience for simulated data, channel\nnames can be provided as a single integer, and the names will be\nautomatically created as sequential integers (starting with ``0``):\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Create some dummy metadata\nn_channels = 32\nsampling_freq = 200 # in Hertz\ninfo = mne.create_info(n_channels, sfreq=sampling_freq)\nprint(info)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can see in the output above that, by default, the channels are assigned\nas type \"misc\" (where it says ``chs: 32 MISC``). You can assign the channel\ntype when initializing the `~mne.Info` object if you want:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "ch_names = [f\"MEG{n:03}\" for n in range(1, 10)] + [\"EOG001\"]\nch_types = [\"mag\", \"grad\", \"grad\"] * 3 + [\"eog\"]\ninfo = mne.create_info(ch_names, ch_types=ch_types, sfreq=sampling_freq)\nprint(info)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If the channel names follow one of the standard montage naming schemes, their\nspatial locations can be automatically added using the\n`~mne.Info.set_montage` method:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "ch_names = [\"Fp1\", \"Fp2\", \"Fz\", \"Cz\", \"Pz\", \"O1\", \"O2\"]\nch_types = [\"eeg\"] * 7\ninfo = mne.create_info(ch_names, ch_types=ch_types, sfreq=sampling_freq)\ninfo.set_montage(\"standard_1020\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ".. admonition:: Info consistency\n :class: sidebar warning\n\n When assigning new values to the fields of an `~mne.Info` object, it is\n important that the fields stay consistent. if there are ``N`` channels:\n\n - The length of the channel information field ``chs`` must be ``N``.\n - The length of the ``ch_names`` field must be ``N``.\n - The ``ch_names`` field should be consistent with the ``name``\n field of the channel information contained in ``chs``.\n\nNote the new field ``dig`` that includes our seven channel locations as well\nas theoretical values for the three\n:term:`cardinal scalp landmarks `.\n\nAdditional fields can be added in the same way that Python dictionaries are\nmodified, using square-bracket key assignment:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "info[\"description\"] = \"My custom dataset\"\ninfo[\"bads\"] = [\"O1\"] # Names of bad channels\nprint(info)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating `~mne.io.Raw` objects\n\n.. admonition:: Units\n :class: sidebar note\n\n The expected units for the different channel types are:\n\n - Volts: eeg, eog, seeg, dbs, emg, ecg, bio, ecog\n - Teslas: mag\n - Teslas/meter: grad\n - Molar: hbo, hbr\n - Amperes: dipole\n - Arbitrary units: misc\n\nTo create a `~mne.io.Raw` object from scratch, you can use the\n`mne.io.RawArray` class constructor, which takes an `~mne.Info` object and a\n:class:`NumPy array ` of shape ``(n_channels, n_samples)``.\nHere, we'll create some sinusoidal data and plot it:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "times = np.linspace(0, 1, sampling_freq, endpoint=False)\nsine = np.sin(20 * np.pi * times)\ncosine = np.cos(10 * np.pi * times)\ndata = np.array([sine, cosine])\n\ninfo = mne.create_info(\n ch_names=[\"10 Hz sine\", \"5 Hz cosine\"], ch_types=[\"misc\"] * 2, sfreq=sampling_freq\n)\n\nsimulated_raw = mne.io.RawArray(data, info)\nsimulated_raw.plot(show_scrollbars=False, show_scalebars=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating `~mne.Epochs` objects\n\nTo create an `~mne.Epochs` object from scratch, you can use the\n`mne.EpochsArray` class constructor, which takes an `~mne.Info` object and a\n:class:`NumPy array ` of shape ``(n_epochs, n_channels,\nn_samples)``. Here we'll create 5 epochs of our 2-channel data, and plot it.\nNotice that we have to pass ``picks='misc'`` to the `~mne.Epochs.plot`\nmethod, because by default it only plots :term:`data channels`.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "data = np.array(\n [\n [0.2 * sine, 1.0 * cosine],\n [0.4 * sine, 0.8 * cosine],\n [0.6 * sine, 0.6 * cosine],\n [0.8 * sine, 0.4 * cosine],\n [1.0 * sine, 0.2 * cosine],\n ]\n)\n\nsimulated_epochs = mne.EpochsArray(data, info)\nsimulated_epochs.plot(picks=\"misc\", show_scrollbars=False, events=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since we did not supply an events array, the `~mne.EpochsArray` constructor\nautomatically created one for us, with all epochs having the same event\nnumber:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(simulated_epochs.events[:, -1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we want to simulate having different experimental conditions, we can pass\nan event array (and an event ID dictionary) to the constructor. Since our\nepochs are 1 second long and have 200 samples/second, we'll put our events\nspaced 200 samples apart, and pass ``tmin=-0.5``, so that the events\nland in the middle of each epoch (the events are always placed at time=0 in\neach epoch).\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "events = np.column_stack(\n (\n np.arange(0, 1000, sampling_freq),\n np.zeros(5, dtype=int),\n np.array([1, 2, 1, 2, 1]),\n )\n)\nevent_dict = dict(condition_A=1, condition_B=2)\nsimulated_epochs = mne.EpochsArray(\n data, info, tmin=-0.5, events=events, event_id=event_dict\n)\nsimulated_epochs.plot(\n picks=\"misc\", show_scrollbars=False, events=events, event_id=event_dict\n)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You could also create simulated epochs by using the normal `~mne.Epochs`\n(not `~mne.EpochsArray`) constructor on the simulated `~mne.io.RawArray`\nobject, by creating an events array (e.g., using\n`mne.make_fixed_length_events`) and extracting epochs around those events.\n\n\n## Creating `~mne.Evoked` Objects\n\nIf you already have data that was averaged across trials, you can use it to\ncreate an `~mne.Evoked` object using the `~mne.EvokedArray` class\nconstructor. It requires an `~mne.Info` object and a data array of shape\n``(n_channels, n_times)``, and has an optional ``tmin`` parameter like\n`~mne.EpochsArray` does. It also has a parameter ``nave`` indicating how many\ntrials were averaged together, and a ``comment`` parameter useful for keeping\ntrack of experimental conditions, etc. Here we'll do the averaging on our\nNumPy array and use the resulting averaged data to make our `~mne.Evoked`.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Create the Evoked object\nevoked_array = mne.EvokedArray(\n data.mean(axis=0), info, tmin=-0.5, nave=data.shape[0], comment=\"simulated\"\n)\nprint(evoked_array)\nevoked_array.plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In certain situations you may wish to use a custom time-frequency\ndecomposition for estimation of power spectra. Or you may wish to\nprocess pre-computed power spectra in MNE.\nFollowing the same logic, it is possible to instantiate averaged power\nspectrum using the :class:`~mne.time_frequency.SpectrumArray` or\n:class:`~mne.time_frequency.EpochsSpectrumArray` classes.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# compute power spectrum\n\npsd, freqs = mne.time_frequency.psd_array_welch(\n data, info[\"sfreq\"], n_fft=128, n_per_seg=32\n)\n\npsd_ave = psd.mean(0)\n\ninfo = mne.create_info([\"Ch 1\", \"Ch2\"], sfreq=sampling_freq, ch_types=\"eeg\")\nspectrum = mne.time_frequency.SpectrumArray(\n data=psd_ave,\n freqs=freqs,\n info=info,\n)\n\nspectrum.plot(spatial_colors=False, amplitude=False)" ] } ], "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 }