From 6fabec2800607ec8dd58110bdc8e8199ff541b76 Mon Sep 17 00:00:00 2001 From: Robert Luke Date: Tue, 23 Mar 2021 10:30:44 +1100 Subject: [PATCH 1/7] Add more sanity checks for fnirs data --- mne/preprocessing/nirs/__init__.py | 2 +- mne/preprocessing/nirs/_optical_density.py | 3 + mne/preprocessing/nirs/_tddr.py | 2 + mne/preprocessing/nirs/nirs.py | 22 +++- .../nirs/tests/test_beer_lambert_law.py | 2 +- mne/preprocessing/nirs/tests/test_nirs.py | 122 +++++++++++++++++- 6 files changed, 147 insertions(+), 6 deletions(-) diff --git a/mne/preprocessing/nirs/__init__.py b/mne/preprocessing/nirs/__init__.py index 6734cf53573..f101867f789 100644 --- a/mne/preprocessing/nirs/__init__.py +++ b/mne/preprocessing/nirs/__init__.py @@ -8,7 +8,7 @@ from .nirs import (short_channels, source_detector_distances, _check_channels_ordered, _channel_frequencies, - _fnirs_check_bads, _fnirs_spread_bads) + _fnirs_check_bads, _fnirs_spread_bads, _channel_chromophore) from ._optical_density import optical_density from ._beer_lambert_law import beer_lambert_law from ._scalp_coupling_index import scalp_coupling_index diff --git a/mne/preprocessing/nirs/_optical_density.py b/mne/preprocessing/nirs/_optical_density.py index 1efa72377a5..feb4ebb6637 100644 --- a/mne/preprocessing/nirs/_optical_density.py +++ b/mne/preprocessing/nirs/_optical_density.py @@ -10,6 +10,7 @@ from ...io.constants import FIFF from ...utils import _validate_type, warn from ...io.pick import _picks_to_idx +from ..nirs import _channel_frequencies, _check_channels_ordered def optical_density(raw): @@ -27,6 +28,8 @@ def optical_density(raw): """ raw = raw.copy().load_data() _validate_type(raw, BaseRaw, 'raw') + _check_channels_ordered(raw, np.unique(_channel_frequencies(raw))) + picks = _picks_to_idx(raw.info, 'fnirs_cw_amplitude') data_means = np.mean(raw.get_data(), axis=1) diff --git a/mne/preprocessing/nirs/_tddr.py b/mne/preprocessing/nirs/_tddr.py index e4daa039188..935a0b98d5a 100644 --- a/mne/preprocessing/nirs/_tddr.py +++ b/mne/preprocessing/nirs/_tddr.py @@ -10,6 +10,7 @@ from ...io import BaseRaw from ...utils import _validate_type, verbose from ...io.pick import _picks_to_idx +from ..nirs import _channel_frequencies, _check_channels_ordered @verbose @@ -42,6 +43,7 @@ def temporal_derivative_distribution_repair(raw, *, verbose=None): """ raw = raw.copy().load_data() _validate_type(raw, BaseRaw, 'raw') + _check_channels_ordered(raw, np.unique(_channel_frequencies(raw))) if not len(pick_types(raw.info, fnirs='fnirs_od')): raise RuntimeError('TDDR should be run on optical density data.') diff --git a/mne/preprocessing/nirs/nirs.py b/mne/preprocessing/nirs/nirs.py index 28dabd1cfdb..6fb7ae38349 100644 --- a/mne/preprocessing/nirs/nirs.py +++ b/mne/preprocessing/nirs/nirs.py @@ -65,9 +65,19 @@ def _channel_frequencies(raw): freqs[ii] = raw.info['chs'][ii]['loc'][9] return freqs +def _channel_chromophore(raw): + """Return the chromophore of each channel.""" + # Only valid for fNIRS data after conversion to haemoglobin + picks = _picks_to_idx(raw.info, ['hbo', 'hbr'], + exclude=[], allow_empty=True) + chroma = [] + for ii in picks: + chroma.append(raw.ch_names[ii].split(" ")[1]) + return chroma + def _check_channels_ordered(raw, freqs): - """Check channels followed expected fNIRS format.""" + """Check channels follow expected fNIRS format.""" # Every second channel should be same SD pair # and have the specified light frequencies. picks = _picks_to_idx(raw.info, ['fnirs_cw_amplitude', 'fnirs_od'], @@ -77,6 +87,13 @@ def _check_channels_ordered(raw, freqs): 'NIRS channels not ordered correctly. An even number of NIRS ' 'channels is required. %d channels were provided: %r' % (len(raw.ch_names), raw.ch_names)) + + all_freqs = [raw.info["chs"][ii]["loc"][9] for ii in picks] + if np.any(np.isnan(all_freqs)): + raise ValueError( + 'NIRS channels is missing wavelength information in the info["chs"]' + f'structure. The encoded wavelengths are {all_freqs}.') + for ii in picks[::2]: ch1_name_info = re.match(r'S(\d+)_D(\d+) (\d+)', raw.info['chs'][ii]['ch_name']) @@ -101,7 +118,8 @@ def _check_channels_ordered(raw, freqs): (int(ch2_name_info.groups()[2]) != freqs[1]): raise ValueError( 'NIRS channels not ordered correctly. Channels must be ordered' - ' as source detector pairs with frequencies: %d & %d' + ' as source detector pairs with alternating' + ' frequencies: %d & %d' % (freqs[0], freqs[1])) return picks diff --git a/mne/preprocessing/nirs/tests/test_beer_lambert_law.py b/mne/preprocessing/nirs/tests/test_beer_lambert_law.py index 2e52d80cf5f..aca6c3d218c 100644 --- a/mne/preprocessing/nirs/tests/test_beer_lambert_law.py +++ b/mne/preprocessing/nirs/tests/test_beer_lambert_law.py @@ -70,7 +70,7 @@ def test_beer_lambert_unordered_errors(): # Test that an error is thrown if inconsistent frequencies used in data raw_od.info['chs'][2]['loc'][9] = 770.0 - with pytest.raises(ValueError, match='pairs with frequencies'): + with pytest.raises(ValueError, match='with alternating frequencies'): beer_lambert_law(raw_od) diff --git a/mne/preprocessing/nirs/tests/test_nirs.py b/mne/preprocessing/nirs/tests/test_nirs.py index 3a85589ef1c..be49666a5bc 100644 --- a/mne/preprocessing/nirs/tests/test_nirs.py +++ b/mne/preprocessing/nirs/tests/test_nirs.py @@ -10,12 +10,13 @@ import numpy as np from numpy.testing import assert_array_equal +from mne import create_info from mne.datasets.testing import data_path -from mne.io import read_raw_nirx +from mne.io import read_raw_nirx, RawArray from mne.preprocessing.nirs import (optical_density, beer_lambert_law, _fnirs_check_bads, _fnirs_spread_bads, _check_channels_ordered, - _channel_frequencies) + _channel_frequencies, _channel_chromophore) from mne.io.pick import _picks_to_idx from mne.datasets import testing @@ -158,6 +159,8 @@ def test_fnirs_channel_naming_and_order_readers(fname): raw = read_raw_nirx(fname) freqs = np.unique(_channel_frequencies(raw)) assert_array_equal(freqs, [760, 850]) + chroma = np.unique(_channel_chromophore(raw)) + assert len(chroma) == 0 picks = _check_channels_ordered(raw, freqs) assert len(picks) == len(raw.ch_names) # as all fNIRS only data @@ -182,9 +185,124 @@ def test_fnirs_channel_naming_and_order_readers(fname): raw = optical_density(raw) freqs = np.unique(_channel_frequencies(raw)) assert_array_equal(freqs, [760, 850]) + chroma = np.unique(_channel_chromophore(raw)) + assert len(chroma) == 0 picks = _check_channels_ordered(raw, freqs) assert len(picks) == len(raw.ch_names) # as all fNIRS only data + # Check on haemoglobin data raw = beer_lambert_law(raw) freqs = np.unique(_channel_frequencies(raw)) assert len(freqs) == 0 + assert len(_channel_chromophore(raw)) == len(raw.ch_names) + chroma = np.unique(_channel_chromophore(raw)) + assert_array_equal(chroma, ["hbo", "hbr"]) + + +def test_fnirs_channel_naming_and_order_custom_raw(): + """Ensure fNIRS channel checking on manually created data.""" + data = np.random.normal(size=(6, 10)) + + # Start with a correctly named raw intensity dataset + # These are the steps required to build an fNIRS Raw object from scratch + ch_names = ['S1_D1 760', 'S1_D1 850', 'S2_D1 760', 'S2_D1 850', + 'S3_D1 760', 'S3_D1 850'] + ch_types = np.repeat("fnirs_cw_amplitude", 6) + info = create_info(ch_names=ch_names, ch_types=ch_types, sfreq=1.0) + raw = RawArray(data, info, verbose=True) + freqs = np.tile([760, 850], 3) + for idx, f in enumerate(freqs): + raw.info["chs"][idx]["loc"][9] = f + + freqs = np.unique(_channel_frequencies(raw)) + picks = _check_channels_ordered(raw, freqs) + assert len(picks) == len(raw.ch_names) + assert len(picks) == 6 + + # Different systems use different frequencies, so ensure that works + ch_names = ['S1_D1 920', 'S1_D1 850', 'S2_D1 920', 'S2_D1 850', + 'S3_D1 920', 'S3_D1 850'] + ch_types = np.repeat("fnirs_cw_amplitude", 6) + info = create_info(ch_names=ch_names, ch_types=ch_types, sfreq=1.0) + raw = RawArray(data, info, verbose=True) + freqs = np.tile([920, 850], 3) + for idx, f in enumerate(freqs): + raw.info["chs"][idx]["loc"][9] = f + + picks = _check_channels_ordered(raw, [920, 850]) + assert len(picks) == len(raw.ch_names) + assert len(picks) == 6 + + # Catch expected errors + + # The frequencies named in the channel names must match the info loc field + ch_names = ['S1_D1 760', 'S1_D1 850', 'S2_D1 760', 'S2_D1 850', + 'S3_D1 760', 'S3_D1 850'] + ch_types = np.repeat("fnirs_cw_amplitude", 6) + info = create_info(ch_names=ch_names, ch_types=ch_types, sfreq=1.0) + raw = RawArray(data, info, verbose=True) + freqs = np.tile([920, 850], 3) + for idx, f in enumerate(freqs): + raw.info["chs"][idx]["loc"][9] = f + with pytest.raises(ValueError, match='name and NIRS frequency do not'): + _check_channels_ordered(raw, [920, 850]) + + # Catch if someone doesnt set the info field + ch_names = ['S1_D1 760', 'S1_D1 850', 'S2_D1 760', 'S2_D1 850', + 'S3_D1 760', 'S3_D1 850'] + ch_types = np.repeat("fnirs_cw_amplitude", 6) + info = create_info(ch_names=ch_names, ch_types=ch_types, sfreq=1.0) + raw = RawArray(data, info, verbose=True) + with pytest.raises(ValueError, match='missing wavelength information'): + _check_channels_ordered(raw, [920, 850]) + + # I have seen data encoded not in alternating frequency, but blocked. + ch_names = ['S1_D1 760', 'S2_D1 760', 'S3_D1 760', + 'S1_D1 850', 'S2_D1 850', 'S3_D1 850'] + ch_types = np.repeat("fnirs_cw_amplitude", 6) + info = create_info(ch_names=ch_names, ch_types=ch_types, sfreq=1.0) + raw = RawArray(data, info, verbose=True) + freqs = np.repeat([760, 850], 3) + for idx, f in enumerate(freqs): + raw.info["chs"][idx]["loc"][9] = f + with pytest.raises(ValueError, match='channels not ordered correctly'): + _check_channels_ordered(raw, [760, 850]) + # and this is how you would fix the ordering, then it should pass + raw.pick(picks=[0, 3, 1, 4, 2, 5]) + _check_channels_ordered(raw, [760, 850]) + + +def test_fnirs_channel_naming_and_order_custom_optical_density(): + """Ensure fNIRS channel checking on manually created data.""" + data = np.random.normal(size=(6, 10)) + + # Start with a correctly named raw intensity dataset + # These are the steps required to build an fNIRS Raw object from scratch + ch_names = ['S1_D1 760', 'S1_D1 850', 'S2_D1 760', 'S2_D1 850', + 'S3_D1 760', 'S3_D1 850'] + ch_types = np.repeat("fnirs_od", 6) + info = create_info(ch_names=ch_names, ch_types=ch_types, sfreq=1.0) + raw = RawArray(data, info, verbose=True) + freqs = np.tile([760, 850], 3) + for idx, f in enumerate(freqs): + raw.info["chs"][idx]["loc"][9] = f + + freqs = np.unique(_channel_frequencies(raw)) + picks = _check_channels_ordered(raw, freqs) + assert len(picks) == len(raw.ch_names) + assert len(picks) == 6 + + # Check block naming for optical density + ch_names = ['S1_D1 760', 'S2_D1 760', 'S3_D1 760', + 'S1_D1 850', 'S2_D1 850', 'S3_D1 850'] + ch_types = np.repeat("fnirs_od", 6) + info = create_info(ch_names=ch_names, ch_types=ch_types, sfreq=1.0) + raw = RawArray(data, info, verbose=True) + freqs = np.repeat([760, 850], 3) + for idx, f in enumerate(freqs): + raw.info["chs"][idx]["loc"][9] = f + with pytest.raises(ValueError, match='channels not ordered correctly'): + _check_channels_ordered(raw, [760, 850]) + # and this is how you would fix the ordering, then it should pass + raw.pick(picks=[0, 3, 1, 4, 2, 5]) + _check_channels_ordered(raw, [760, 850]) \ No newline at end of file From 0d66ab95c0fbe7873e5ad182847ef048ac74cfe6 Mon Sep 17 00:00:00 2001 From: Robert Luke Date: Tue, 23 Mar 2021 10:55:02 +1100 Subject: [PATCH 2/7] Add description of fnirs fields --- mne/preprocessing/nirs/nirs.py | 5 ++-- mne/preprocessing/nirs/tests/test_nirs.py | 2 +- tutorials/io/plot_30_reading_fnirs_data.py | 34 +++++++++++++++++++--- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/mne/preprocessing/nirs/nirs.py b/mne/preprocessing/nirs/nirs.py index 6fb7ae38349..eea96594fa8 100644 --- a/mne/preprocessing/nirs/nirs.py +++ b/mne/preprocessing/nirs/nirs.py @@ -65,6 +65,7 @@ def _channel_frequencies(raw): freqs[ii] = raw.info['chs'][ii]['loc'][9] return freqs + def _channel_chromophore(raw): """Return the chromophore of each channel.""" # Only valid for fNIRS data after conversion to haemoglobin @@ -91,8 +92,8 @@ def _check_channels_ordered(raw, freqs): all_freqs = [raw.info["chs"][ii]["loc"][9] for ii in picks] if np.any(np.isnan(all_freqs)): raise ValueError( - 'NIRS channels is missing wavelength information in the info["chs"]' - f'structure. The encoded wavelengths are {all_freqs}.') + 'NIRS channels is missing wavelength information in the' + f'info["chs"] structure. The encoded wavelengths are {all_freqs}.') for ii in picks[::2]: ch1_name_info = re.match(r'S(\d+)_D(\d+) (\d+)', diff --git a/mne/preprocessing/nirs/tests/test_nirs.py b/mne/preprocessing/nirs/tests/test_nirs.py index be49666a5bc..246e03b624b 100644 --- a/mne/preprocessing/nirs/tests/test_nirs.py +++ b/mne/preprocessing/nirs/tests/test_nirs.py @@ -305,4 +305,4 @@ def test_fnirs_channel_naming_and_order_custom_optical_density(): _check_channels_ordered(raw, [760, 850]) # and this is how you would fix the ordering, then it should pass raw.pick(picks=[0, 3, 1, 4, 2, 5]) - _check_channels_ordered(raw, [760, 850]) \ No newline at end of file + _check_channels_ordered(raw, [760, 850]) diff --git a/tutorials/io/plot_30_reading_fnirs_data.py b/tutorials/io/plot_30_reading_fnirs_data.py index 1bb8d851ea0..5d64be52d3b 100644 --- a/tutorials/io/plot_30_reading_fnirs_data.py +++ b/tutorials/io/plot_30_reading_fnirs_data.py @@ -68,9 +68,35 @@ A channel is formed by source-detector pairs. MNE stores the location of the channels, sources, and detectors. - -.. warning:: Information about device light wavelength is stored in - channel names. Manual modification of channel names is not - recommended. +.. warning:: Manual modification of channel names and info fields is not + recommended. fNIRS data is encoded in MNE using a combination + of the channel name and the info structure. Additionally, there + is an expected order of channels that must be maintained. Any + change to the expected structure will cause an error. + + Channels names must follow the structure + ``S#_D# value`` + where S# is the source number (e.g. S12 for source 12), + D# is the detector number (e.g. D4 for detector 4), + and the value is either the light wavelength if the data type is + raw intensity or optical density, or the chromophore type if the + data is hbo or hbr. For example ``S1_D2 760`` is valid as is + ``S11_D1 850`` and ``S1_D2 hbo`` and ``S1_D2 hbr``. However, + these examples are not valid ``D1_S2 hbo``, ``S1_D2_760``. + + Channels with the same source-detector pairing must be stored + in consecutive order. For example + ``["S11_D2 hbo", "S11_D2 hbr", "S1_D2 hbo", "S11_D2 hbr"]`` + is acceptable, but + ``["S11_D2 hbo", "S1_D2 hbo", "S11_D2 hbr", "S11_D2 hbr"]`` + is not. Further, the order of type must be maintained for all + fNIRS channels so the following is not valid + ``["S11_D2 hbo", "S11_D2 hbr", "S1_D2 hbr", "S11_D2 hbo"]``. + + For raw amplitude measurements and for optical density data + the wavelength information must be stored in + ``info["chs"][ii]["loc"][9]`` + and it must match the channel name. For example the channel + ``S11_D2 760`` must have the value 760 stored in the loc[9] field. """ # noqa:E501 From 0312f8d3e753c5d50e0640a8d8b8e04e4f7cbebd Mon Sep 17 00:00:00 2001 From: Robert Luke Date: Tue, 23 Mar 2021 10:59:02 +1100 Subject: [PATCH 3/7] whitespace --- tutorials/io/plot_30_reading_fnirs_data.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tutorials/io/plot_30_reading_fnirs_data.py b/tutorials/io/plot_30_reading_fnirs_data.py index 5d64be52d3b..c16ba5f8c66 100644 --- a/tutorials/io/plot_30_reading_fnirs_data.py +++ b/tutorials/io/plot_30_reading_fnirs_data.py @@ -73,7 +73,7 @@ of the channel name and the info structure. Additionally, there is an expected order of channels that must be maintained. Any change to the expected structure will cause an error. - + Channels names must follow the structure ``S#_D# value`` where S# is the source number (e.g. S12 for source 12), @@ -83,16 +83,16 @@ data is hbo or hbr. For example ``S1_D2 760`` is valid as is ``S11_D1 850`` and ``S1_D2 hbo`` and ``S1_D2 hbr``. However, these examples are not valid ``D1_S2 hbo``, ``S1_D2_760``. - + Channels with the same source-detector pairing must be stored in consecutive order. For example ``["S11_D2 hbo", "S11_D2 hbr", "S1_D2 hbo", "S11_D2 hbr"]`` - is acceptable, but + is acceptable, but ``["S11_D2 hbo", "S1_D2 hbo", "S11_D2 hbr", "S11_D2 hbr"]`` is not. Further, the order of type must be maintained for all fNIRS channels so the following is not valid ``["S11_D2 hbo", "S11_D2 hbr", "S1_D2 hbr", "S11_D2 hbo"]``. - + For raw amplitude measurements and for optical density data the wavelength information must be stored in ``info["chs"][ii]["loc"][9]`` From ea906973dcc7f87fb8a7682155feb00aaa10fc5a Mon Sep 17 00:00:00 2001 From: Robert Luke Date: Tue, 23 Mar 2021 11:20:48 +1100 Subject: [PATCH 4/7] Text tweaks --- mne/preprocessing/nirs/tests/test_nirs.py | 2 +- tutorials/io/plot_30_reading_fnirs_data.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mne/preprocessing/nirs/tests/test_nirs.py b/mne/preprocessing/nirs/tests/test_nirs.py index 246e03b624b..55845521f0b 100644 --- a/mne/preprocessing/nirs/tests/test_nirs.py +++ b/mne/preprocessing/nirs/tests/test_nirs.py @@ -247,7 +247,7 @@ def test_fnirs_channel_naming_and_order_custom_raw(): with pytest.raises(ValueError, match='name and NIRS frequency do not'): _check_channels_ordered(raw, [920, 850]) - # Catch if someone doesnt set the info field + # Catch if someone doesn't set the info field ch_names = ['S1_D1 760', 'S1_D1 850', 'S2_D1 760', 'S2_D1 850', 'S3_D1 760', 'S3_D1 850'] ch_types = np.repeat("fnirs_cw_amplitude", 6) diff --git a/tutorials/io/plot_30_reading_fnirs_data.py b/tutorials/io/plot_30_reading_fnirs_data.py index c16ba5f8c66..e92c1722fd6 100644 --- a/tutorials/io/plot_30_reading_fnirs_data.py +++ b/tutorials/io/plot_30_reading_fnirs_data.py @@ -74,7 +74,7 @@ is an expected order of channels that must be maintained. Any change to the expected structure will cause an error. - Channels names must follow the structure + Channel names must follow the structure ``S#_D# value`` where S# is the source number (e.g. S12 for source 12), D# is the detector number (e.g. D4 for detector 4), @@ -86,17 +86,19 @@ Channels with the same source-detector pairing must be stored in consecutive order. For example - ``["S11_D2 hbo", "S11_D2 hbr", "S1_D2 hbo", "S11_D2 hbr"]`` + ``["S11_D2 hbo", "S11_D2 hbr", "S1_D2 hbo", "S1_D2 hbr"]`` is acceptable, but - ``["S11_D2 hbo", "S1_D2 hbo", "S11_D2 hbr", "S11_D2 hbr"]`` + ``["S11_D2 hbo", "S1_D2 hbo", "S11_D2 hbr", "S1_D2 hbr"]`` is not. Further, the order of type must be maintained for all fNIRS channels so the following is not valid - ``["S11_D2 hbo", "S11_D2 hbr", "S1_D2 hbr", "S11_D2 hbo"]``. + ``["S11_D2 hbo", "S11_D2 hbr", "S1_D2 hbr", "S1_D2 hbo"]``. For raw amplitude measurements and for optical density data the wavelength information must be stored in ``info["chs"][ii]["loc"][9]`` and it must match the channel name. For example the channel ``S11_D2 760`` must have the value 760 stored in the loc[9] field. + There is no such requirement for hbo and hbr data as they have + a dedicated channel type. """ # noqa:E501 From a54f79adcea5513aa848062b6c097f93e59cc899 Mon Sep 17 00:00:00 2001 From: Robert Luke Date: Tue, 23 Mar 2021 11:21:44 +1100 Subject: [PATCH 5/7] Text tweaks --- tutorials/io/plot_30_reading_fnirs_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/io/plot_30_reading_fnirs_data.py b/tutorials/io/plot_30_reading_fnirs_data.py index e92c1722fd6..eaf2eda5249 100644 --- a/tutorials/io/plot_30_reading_fnirs_data.py +++ b/tutorials/io/plot_30_reading_fnirs_data.py @@ -97,7 +97,7 @@ the wavelength information must be stored in ``info["chs"][ii]["loc"][9]`` and it must match the channel name. For example the channel - ``S11_D2 760`` must have the value 760 stored in the loc[9] field. + ``S11_D2 760`` must have the value 760 stored in the ``loc[9]`` field. There is no such requirement for hbo and hbr data as they have a dedicated channel type. From d42951dcd0f4eb89ffc376befeba5a35a6c680b1 Mon Sep 17 00:00:00 2001 From: Robert Luke Date: Tue, 23 Mar 2021 12:02:14 +1100 Subject: [PATCH 6/7] Remove tutorial text and defer to discussion in #9141 --- tutorials/io/plot_30_reading_fnirs_data.py | 35 ++-------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/tutorials/io/plot_30_reading_fnirs_data.py b/tutorials/io/plot_30_reading_fnirs_data.py index eaf2eda5249..21c0f0f7b1d 100644 --- a/tutorials/io/plot_30_reading_fnirs_data.py +++ b/tutorials/io/plot_30_reading_fnirs_data.py @@ -68,37 +68,8 @@ A channel is formed by source-detector pairs. MNE stores the location of the channels, sources, and detectors. -.. warning:: Manual modification of channel names and info fields is not - recommended. fNIRS data is encoded in MNE using a combination - of the channel name and the info structure. Additionally, there - is an expected order of channels that must be maintained. Any - change to the expected structure will cause an error. - - Channel names must follow the structure - ``S#_D# value`` - where S# is the source number (e.g. S12 for source 12), - D# is the detector number (e.g. D4 for detector 4), - and the value is either the light wavelength if the data type is - raw intensity or optical density, or the chromophore type if the - data is hbo or hbr. For example ``S1_D2 760`` is valid as is - ``S11_D1 850`` and ``S1_D2 hbo`` and ``S1_D2 hbr``. However, - these examples are not valid ``D1_S2 hbo``, ``S1_D2_760``. - - Channels with the same source-detector pairing must be stored - in consecutive order. For example - ``["S11_D2 hbo", "S11_D2 hbr", "S1_D2 hbo", "S1_D2 hbr"]`` - is acceptable, but - ``["S11_D2 hbo", "S1_D2 hbo", "S11_D2 hbr", "S1_D2 hbr"]`` - is not. Further, the order of type must be maintained for all - fNIRS channels so the following is not valid - ``["S11_D2 hbo", "S11_D2 hbr", "S1_D2 hbr", "S1_D2 hbo"]``. - - For raw amplitude measurements and for optical density data - the wavelength information must be stored in - ``info["chs"][ii]["loc"][9]`` - and it must match the channel name. For example the channel - ``S11_D2 760`` must have the value 760 stored in the ``loc[9]`` field. - There is no such requirement for hbo and hbr data as they have - a dedicated channel type. +.. warning:: Information about device light wavelength is stored in + channel names. Manual modification of channel names is not + recommended. """ # noqa:E501 From 016434c63e62cf996aeb7ec53f848addcd5eb296 Mon Sep 17 00:00:00 2001 From: Robert Luke Date: Tue, 23 Mar 2021 12:02:56 +1100 Subject: [PATCH 7/7] Line missing --- tutorials/io/plot_30_reading_fnirs_data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tutorials/io/plot_30_reading_fnirs_data.py b/tutorials/io/plot_30_reading_fnirs_data.py index 21c0f0f7b1d..1bb8d851ea0 100644 --- a/tutorials/io/plot_30_reading_fnirs_data.py +++ b/tutorials/io/plot_30_reading_fnirs_data.py @@ -68,6 +68,7 @@ A channel is formed by source-detector pairs. MNE stores the location of the channels, sources, and detectors. + .. warning:: Information about device light wavelength is stored in channel names. Manual modification of channel names is not recommended.