{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n\n# Importing data from fNIRS devices\n\nfNIRS devices consist of two kinds of optodes: light sources (AKA \"emitters\" or\n\"transmitters\") and light detectors (AKA \"receivers\"). Channels are defined as\nsource-detector pairs, and channel locations are defined as the midpoint\nbetween source and detector.\n\nMNE-Python provides functions for reading fNIRS data and optode locations from\nseveral file formats. Regardless of the device manufacturer or file format,\nMNE-Python's fNIRS functions will internally store the measurement data and its\nmetadata in the same way (e.g., data values are always converted into SI\nunits). Supported measurement types include amplitude, optical density,\noxyhaemoglobin concentration, and deoxyhemoglobin concentration (for continuous\nwave fNIRS), and additionally AC amplitude and phase (for\nfrequency domain fNIRS).\n\n

Warning

MNE-Python stores metadata internally with a specific structure,\n and internal functions expect specific naming conventions.\n Manual modification of channel names and metadata\n is not recommended.

\n\n\n## Standardized data\n\n### SNIRF (.snirf)\n\nThe Shared Near Infrared Spectroscopy Format\n([SNIRF](https://github.com/fNIRS/snirf/blob/master/snirf_specification.md)_)\nis designed by the fNIRS community in an effort to facilitate\nsharing and analysis of fNIRS data. And is the official format of the\nSociety for functional near-infrared spectroscopy (SfNIRS).\nThe manufacturers Gowerlabs, NIRx, Kernel, Artinis, and Cortivision\nexport data in the SNIRF format, and these files can be imported in to MNE.\nSNIRF is the preferred format for reading data in to MNE-Python.\nData stored in the SNIRF format can be read in\nusing :func:`mne.io.read_raw_snirf`.\n\n

Note

The SNIRF format has provisions for many different types of fNIRS\n recordings. MNE-Python currently only supports reading continuous\n wave or haemoglobin data stored in the .snirf format.

\n\n\n#### Specifying the coordinate system\n\nThere are a variety of coordinate systems used to specify the location of\nsensors (see `tut-source-alignment` for details). Where possible the\ncoordinate system will be determined automatically when reading a SNIRF file.\nHowever, sometimes this is not possible and you must manually specify the\ncoordinate frame the optodes are in. This is done using the ``optode_frame``\nargument when loading data.\n\n======= ================== =================\nVendor Model ``optode_frame``\n======= ================== =================\nNIRx ICBM-152 MNI mri\nKernel ICBM 2009b mri\n======= ================== =================\n\nThe coordinate system is automatically detected for Gowerlabs SNIRF files.\n\n\n## Continuous Wave Devices\n\n\n### NIRx (directory or hdr)\n\nNIRx produce continuous wave fNIRS devices.\nNIRx recordings can be read in using :func:`mne.io.read_raw_nirx`.\nThe NIRx device stores data directly to a directory with multiple file types,\nMNE-Python extracts the appropriate information from each file.\nMNE-Python only supports NIRx files recorded with NIRStar\nversion 15.0 and above and Aurora version 2021 and above.\nMNE-Python supports reading data from NIRScout and NIRSport devices.\n\n\n\n### Hitachi (.csv)\n\nHitachi produce continuous wave fNIRS devices.\nHitachi fNIRS recordings can be read using :func:`mne.io.read_raw_hitachi`.\nNo optode information is stored so you'll need to set the montage manually,\nsee the Notes section of :func:`mne.io.read_raw_hitachi`.\n\n\n## Frequency Domain Devices\n\n\n### BOXY (.txt)\n\nBOXY recordings can be read in using :func:`mne.io.read_raw_boxy`.\nThe BOXY software and ISS Imagent I and II devices are frequency domain\nsystems that store data in a single ``.txt`` file containing what they call\n(with MNE-Python's name for that type of data in parens):\n\n- DC\n All light collected by the detector (``fnirs_cw_amplitude``)\n- AC\n High-frequency modulated light intensity (``fnirs_fd_ac_amplitude``)\n- Phase\n Phase of the modulated light (``fnirs_fd_phase``)\n\nDC data is stored as the type ``fnirs_cw_amplitude`` because it\ncollects both the modulated and any unmodulated light, and hence is analogous\nto what is collected by continuous wave systems such as NIRx. This helps with\nconformance to SNIRF standard types.\n\nThese raw data files can be saved by the acquisition devices as parsed or\nunparsed ``.txt`` files, which affects how the data in the file is organised.\nMNE-Python will read either file type and extract the raw DC, AC,\nand Phase data. If triggers are sent using the ``digaux`` port of the\nrecording hardware, MNE-Python will also read the ``digaux`` data and\ncreate annotations for any triggers.\n\n\n## Custom Data Import\n\n### Loading legacy data in CSV or TSV format\n\n

Warning

This method is not supported and users are discouraged to use it.\n You should convert your data to the\n [SNIRF](https://github.com/fNIRS/snirf) format using the tools\n provided by the Society for functional Near-Infrared Spectroscopy,\n and then load it using :func:`mne.io.read_raw_snirf`.

\n\nfNIRS measurements may be stored in a non-standardised format that is not\nsupported by MNE-Python and cannot be converted easily into SNIRF.\nThis legacy data is often in CSV or TSV format,\nwe show here a way to load it even though it is not officially supported by\nMNE-Python due to the lack of standardisation of the file format (the\nnaming and ordering of channels, the type and scaling of data, and\nspecification of sensor positions varies between each vendor). You will likely\nhave to adapt this depending on the system from which your CSV originated.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\nimport pandas as pd\n\nimport mne" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, we generate an example CSV file which will then be loaded in to\nMNE-Python. This step would be skipped if you have actual data you wish to\nload. We simulate 16 channels with 100 samples of data and save this to a\nfile called fnirs.csv.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "pd.DataFrame(np.random.normal(size=(16, 100))).to_csv(\"fnirs.csv\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we will load the example CSV file.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "data = pd.read_csv(\"fnirs.csv\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, the metadata must be specified manually as the CSV file does not\ncontain information about channel names, types, sample rate etc.\n\n

Warning

In MNE-Python the naming of channels MUST follow the structure\n ``S#_D# type`` where # is replaced by the appropriate source and\n detector numbers and type is either ``hbo``, ``hbr`` or the\n wavelength.

\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "ch_names = [\n \"S1_D1 hbo\",\n \"S1_D1 hbr\",\n \"S2_D1 hbo\",\n \"S2_D1 hbr\",\n \"S3_D1 hbo\",\n \"S3_D1 hbr\",\n \"S4_D1 hbo\",\n \"S4_D1 hbr\",\n \"S5_D2 hbo\",\n \"S5_D2 hbr\",\n \"S6_D2 hbo\",\n \"S6_D2 hbr\",\n \"S7_D2 hbo\",\n \"S7_D2 hbr\",\n \"S8_D2 hbo\",\n \"S8_D2 hbr\",\n]\nch_types = [\n \"hbo\",\n \"hbr\",\n \"hbo\",\n \"hbr\",\n \"hbo\",\n \"hbr\",\n \"hbo\",\n \"hbr\",\n \"hbo\",\n \"hbr\",\n \"hbo\",\n \"hbr\",\n \"hbo\",\n \"hbr\",\n \"hbo\",\n \"hbr\",\n]\nsfreq = 10.0 # in Hz" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, the data can be converted in to an MNE-Python data structure.\nThe metadata above is used to create an :class:`mne.Info` data structure,\nand this is combined with the data to create an MNE-Python\n:class:`~mne.io.Raw` object. For more details on the info structure\nsee `tut-info-class`, and for additional details on how continuous data\nis stored in MNE-Python see `tut-raw-class`.\nFor a more extensive description of how to create MNE-Python data structures\nfrom raw array data see `tut-creating-data-structures`.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "info = mne.create_info(ch_names=ch_names, ch_types=ch_types, sfreq=sfreq)\nraw = mne.io.RawArray(data, info, verbose=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Applying standard sensor locations to imported data\n\nHaving information about optode locations may assist in your analysis.\nBeyond the general benefits this provides (e.g. creating regions of interest,\netc), this is may be particularly important for fNIRS as information about\nthe optode locations is required to convert the optical density data in to an\nestimate of the haemoglobin concentrations.\nMNE-Python provides methods to load standard sensor configurations\n(montages) from some vendors, and this is demonstrated below.\nSome handy tutorials for understanding sensor locations, coordinate systems,\nand how to store and view this information in MNE-Python are:\n`tut-sensor-locations`, `tut-source-alignment`, and\n`ex-eeg-on-scalp`.\n\nBelow is an example of how to load the optode positions for an Artinis\nOctaMon device.\n\n

Note

It is also possible to create a custom montage from a file for\n fNIRS with :func:`mne.channels.read_custom_montage` by setting\n ``coord_frame`` to ``'mri'``.

\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "montage = mne.channels.make_standard_montage(\"artinis-octamon\")\nraw.set_montage(montage)\n\n# View the position of optodes in 2D to confirm the positions are correct.\nraw.plot_sensors()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To validate the positions were loaded correctly it is also possible to view\nthe location of the sources (red), detectors (black), and channels (white\nlines and orange dots) in a 3D representation.\nThe ficiduals are marked in blue, green and red.\nSee `tut-source-alignment` for more details.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "subjects_dir = mne.datasets.sample.data_path() / \"subjects\"\nmne.datasets.fetch_fsaverage(subjects_dir=subjects_dir)\n\nbrain = mne.viz.Brain(\n \"fsaverage\", subjects_dir=subjects_dir, alpha=0.5, cortex=\"low_contrast\"\n)\nbrain.add_head()\nbrain.add_sensors(raw.info, trans=\"fsaverage\")\nbrain.show_view(azimuth=90, elevation=90, distance=500)" ] } ], "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 }