From fd3112ef5b8bf4f23f4dda320ef91b1b22e7f7cb Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Mon, 16 May 2022 13:49:42 -0400 Subject: [PATCH] BUG: Fix bug with fNIRS reordering (#10630) * BUG: Fix bug with ordering * DOC: Document temporary status --- doc/overview/faq.rst | 2 +- mne/channels/tests/test_interpolation.py | 2 +- mne/io/nirx/nirx.py | 23 +++- mne/io/nirx/tests/test_nirx.py | 106 +++++++++++------- mne/io/snirf/_snirf.py | 5 +- mne/io/snirf/tests/test_snirf.py | 31 ++++- .../nirs/tests/test_beer_lambert_law.py | 17 +-- ...temporal_derivative_distribution_repair.py | 2 +- tutorials/io/30_reading_fnirs_data.py | 10 +- 9 files changed, 129 insertions(+), 69 deletions(-) diff --git a/doc/overview/faq.rst b/doc/overview/faq.rst index d5351cd9986..14c574bb3cb 100644 --- a/doc/overview/faq.rst +++ b/doc/overview/faq.rst @@ -414,7 +414,7 @@ References .. _`the most current version`: https://github.com/mne-tools/mne-python/releases/latest .. _`minimal working example`: https://en.wikipedia.org/wiki/Minimal_Working_Example -.. _mri_watershed: https://freesurfer.net/fswiki/mri_watershed +.. _mri_watershed: https://surfer.nmr.mgh.harvard.edu/fswiki/mri_watershed .. _mri_normalize: https://surfer.nmr.mgh.harvard.edu/fswiki/mri_normalize .. _freeview: https://surfer.nmr.mgh.harvard.edu/fswiki/FreeviewGuide/FreeviewIntroduction .. _`FreeSurfer listserv`: https://www.mail-archive.com/freesurfer@nmr.mgh.harvard.edu/ diff --git a/mne/channels/tests/test_interpolation.py b/mne/channels/tests/test_interpolation.py index 8ca35ad3326..37da056d260 100644 --- a/mne/channels/tests/test_interpolation.py +++ b/mne/channels/tests/test_interpolation.py @@ -302,6 +302,6 @@ def test_interpolation_nirs(): assert bad_0_std_pre_interp > np.std(raw_od._data[bad_0]) raw_haemo = beer_lambert_law(raw_od, ppf=6) raw_haemo.info['bads'] = raw_haemo.ch_names[2:4] - assert raw_haemo.info['bads'] == ['S10_D11 hbo', 'S10_D11 hbr'] + assert raw_haemo.info['bads'] == ['S1_D2 hbo', 'S1_D2 hbr'] raw_haemo.interpolate_bads() assert raw_haemo.info['bads'] == [] diff --git a/mne/io/nirx/nirx.py b/mne/io/nirx/nirx.py index c025f2b8942..b03e8beb999 100644 --- a/mne/io/nirx/nirx.py +++ b/mne/io/nirx/nirx.py @@ -16,6 +16,7 @@ from ..utils import _mult_cal_one from ..constants import FIFF from ..meas_info import create_info, _format_dig_points +from ..pick import pick_types from ...annotations import Annotations from ..._freesurfer import get_mni_fiducials from ...transforms import apply_trans, _get_trans @@ -458,9 +459,7 @@ def __init__(self, fname, saturated, preload=False, verbose=None): ch_names.append(list()) annot = Annotations(onset, duration, description, ch_names=ch_names) self.set_annotations(annot) - - sort_idx = np.argsort(self.ch_names) - self.pick(picks=sort_idx) + self.pick(picks=_nirs_sort_idx(self.info)) def _read_segment_file(self, data, idx, fi, start, stop, cals, mult): """Read a segment of data from a file. @@ -513,3 +512,21 @@ def _convert_fnirs_to_head(trans, fro, to, src_locs, det_locs, ch_locs): det_locs = apply_trans(mri_head_t, det_locs) ch_locs = apply_trans(mri_head_t, ch_locs) return src_locs, det_locs, ch_locs, mri_head_t + + +def _nirs_sort_idx(info): + # TODO: Remove any actual reordering that is done and just use this + # function to get picks to operate on in an ordered way. This should be + # done by refactoring mne.preprocessing.nirs.nirs._check_channels_ordered + # and this function to make sure the picks we obtain here are in the + # correct order. + nirs_picks = pick_types(info, fnirs=True, exclude=()) + other_picks = np.setdiff1d(np.arange(info['nchan']), nirs_picks) + prefixes = [info['ch_names'][pick].split()[0] for pick in nirs_picks] + nirs_names = [info['ch_names'][pick] for pick in nirs_picks] + nirs_sorted = sorted(nirs_names, + key=lambda name: (prefixes.index(name.split()[0]), + name.split(maxsplit=1)[1])) + nirs_picks = nirs_picks[ + [nirs_names.index(name) for name in nirs_sorted]] + return np.concatenate((nirs_picks, other_picks)) diff --git a/mne/io/nirx/tests/test_nirx.py b/mne/io/nirx/tests/test_nirx.py index 7d5b7edb153..263c2e3dc32 100644 --- a/mne/io/nirx/tests/test_nirx.py +++ b/mne/io/nirx/tests/test_nirx.py @@ -71,6 +71,7 @@ def test_nirsport_v2_matches_snirf(fname_nirx, fname_snirf): """Test NIRSport2 raw files return same data as snirf.""" raw = read_raw_nirx(fname_nirx, preload=True) raw_snirf = read_raw_snirf(fname_snirf, preload=True) + assert raw.ch_names == raw_snirf.ch_names assert_allclose(raw._data, raw_snirf._data) @@ -119,9 +120,9 @@ def test_nirsport_v2(): assert_allclose( mni_locs[2], [-0.0841, -0.0138, 0.0248], atol=allowed_dist_error) - assert raw.info['ch_names'][13][3:5] == 'D5' + assert raw.info['ch_names'][34][3:5] == 'D5' assert_allclose( - mni_locs[13], [0.0845, -0.0451, -0.0123], atol=allowed_dist_error) + mni_locs[34], [0.0845, -0.0451, -0.0123], atol=allowed_dist_error) # Test location of sensors # The locations of sensors can be seen in the second @@ -141,7 +142,7 @@ def test_nirsport_v2(): assert raw.info['ch_names'][39][:2] == 'S8' assert_allclose( - mni_locs[39], [0.0828, -0.046, 0.0285], atol=allowed_dist_error) + mni_locs[34], [0.0828, -0.046, 0.0285], atol=allowed_dist_error) assert len(raw.annotations) == 3 assert raw.annotations.description[0] == '1.0' @@ -291,7 +292,7 @@ def test_nirx_15_2_short(): # Test channel naming assert raw.info['ch_names'][:4] == ["S1_D1 760", "S1_D1 850", "S1_D9 760", "S1_D9 850"] - assert raw.info['ch_names'][24:26] == ["S5_D8 760", "S5_D8 850"] + assert raw.info['ch_names'][24:26] == ["S5_D13 760", "S5_D13 850"] # Test frequency encoding assert raw.info['chs'][0]['loc'][9] == 760 @@ -319,7 +320,7 @@ def test_nirx_15_2_short(): # These are the ones marked as red at # https://github.com/mne-tools/mne-testing-data/pull/51 step 4 figure 2 is_short = short_channels(raw.info) - assert_array_equal(is_short[:9:2], [False, True, True, False, True]) + assert_array_equal(is_short[:9:2], [False, True, False, True, False]) is_short = short_channels(raw.info, threshold=0.003) assert_array_equal(is_short[:3:2], [False, False]) is_short = short_channels(raw.info, threshold=50) @@ -346,29 +347,29 @@ def test_nirx_15_2_short(): assert_allclose( mni_locs[0], [-0.0841, -0.0464, -0.0129], atol=allowed_dist_error) - assert raw.info['ch_names'][6][3:5] == 'D3' + assert raw.info['ch_names'][4][3:5] == 'D3' assert_allclose( - mni_locs[6], [0.0846, -0.0142, -0.0156], atol=allowed_dist_error) + mni_locs[4], [0.0846, -0.0142, -0.0156], atol=allowed_dist_error) - assert raw.info['ch_names'][10][3:5] == 'D2' + assert raw.info['ch_names'][8][3:5] == 'D2' assert_allclose( - mni_locs[10], [0.0207, -0.1062, 0.0484], atol=allowed_dist_error) + mni_locs[8], [0.0207, -0.1062, 0.0484], atol=allowed_dist_error) - assert raw.info['ch_names'][14][3:5] == 'D4' + assert raw.info['ch_names'][12][3:5] == 'D4' assert_allclose( - mni_locs[14], [-0.0196, 0.0821, 0.0275], atol=allowed_dist_error) + mni_locs[12], [-0.0196, 0.0821, 0.0275], atol=allowed_dist_error) - assert raw.info['ch_names'][18][3:5] == 'D5' + assert raw.info['ch_names'][16][3:5] == 'D5' assert_allclose( - mni_locs[18], [-0.0360, 0.0276, 0.0778], atol=allowed_dist_error) + mni_locs[16], [-0.0360, 0.0276, 0.0778], atol=allowed_dist_error) - assert raw.info['ch_names'][20][3:5] == 'D6' + assert raw.info['ch_names'][19][3:5] == 'D6' assert_allclose( - mni_locs[20], [0.0352, 0.0283, 0.0780], atol=allowed_dist_error) + mni_locs[19], [0.0352, 0.0283, 0.0780], atol=allowed_dist_error) - assert raw.info['ch_names'][23][3:5] == 'D7' + assert raw.info['ch_names'][21][3:5] == 'D7' assert_allclose( - mni_locs[23], [0.0388, -0.0477, 0.0932], atol=allowed_dist_error) + mni_locs[21], [0.0388, -0.0477, 0.0932], atol=allowed_dist_error) @requires_testing_data @@ -383,7 +384,7 @@ def test_nirx_15_3_short(): # Test channel naming assert raw.info['ch_names'][:4] == ["S1_D2 760", "S1_D2 850", "S1_D9 760", "S1_D9 850"] - assert raw.info['ch_names'][24:26] == ["S5_D8 760", "S5_D8 850"] + assert raw.info['ch_names'][24:26] == ["S5_D13 760", "S5_D13 850"] # Test frequency encoding assert raw.info['chs'][0]['loc'][9] == 760 @@ -411,7 +412,7 @@ def test_nirx_15_3_short(): # These are the ones marked as red at # https://github.com/mne-tools/mne-testing-data/pull/72 is_short = short_channels(raw.info) - assert_array_equal(is_short[:9:2], [False, True, False, True, True]) + assert_array_equal(is_short[:9:2], [False, True, False, True, False]) is_short = short_channels(raw.info, threshold=0.003) assert_array_equal(is_short[:3:2], [False, False]) is_short = short_channels(raw.info, threshold=50) @@ -438,25 +439,25 @@ def test_nirx_15_3_short(): assert_allclose( mni_locs[4], [0.0846, -0.0142, -0.0156], atol=allowed_dist_error) - assert raw.info['ch_names'][10][3:5] == 'D3' + assert raw.info['ch_names'][8][3:5] == 'D3' assert_allclose( - mni_locs[10], [0.0207, -0.1062, 0.0484], atol=allowed_dist_error) + mni_locs[8], [0.0207, -0.1062, 0.0484], atol=allowed_dist_error) - assert raw.info['ch_names'][14][3:5] == 'D4' + assert raw.info['ch_names'][12][3:5] == 'D4' assert_allclose( - mni_locs[14], [-0.0196, 0.0821, 0.0275], atol=allowed_dist_error) + mni_locs[12], [-0.0196, 0.0821, 0.0275], atol=allowed_dist_error) - assert raw.info['ch_names'][18][3:5] == 'D5' + assert raw.info['ch_names'][16][3:5] == 'D5' assert_allclose( - mni_locs[18], [-0.0360, 0.0276, 0.0778], atol=allowed_dist_error) + mni_locs[16], [-0.0360, 0.0276, 0.0778], atol=allowed_dist_error) - assert raw.info['ch_names'][20][3:5] == 'D6' + assert raw.info['ch_names'][19][3:5] == 'D6' assert_allclose( - mni_locs[20], [0.0388, -0.0477, 0.0932], atol=allowed_dist_error) + mni_locs[19], [0.0388, -0.0477, 0.0932], atol=allowed_dist_error) - assert raw.info['ch_names'][22][3:5] == 'D7' + assert raw.info['ch_names'][21][3:5] == 'D7' assert_allclose( - mni_locs[22], [-0.0394, -0.0483, 0.0928], atol=allowed_dist_error) + mni_locs[21], [-0.0394, -0.0483, 0.0928], atol=allowed_dist_error) @requires_testing_data @@ -504,8 +505,8 @@ def test_nirx_15_2(): tzinfo=dt.timezone.utc) # Test channel naming - assert raw.info['ch_names'][:4] == ["S10_D10 760", "S10_D10 850", - "S10_D9 760", "S10_D9 850"] + assert raw.info['ch_names'][:4] == ["S1_D1 760", "S1_D1 850", + "S1_D10 760", "S1_D10 850"] # Test info import assert raw.info['subject_info'] == dict(sex=1, first_name="TestRecording", @@ -522,13 +523,13 @@ def test_nirx_15_2(): head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) - assert raw.info['ch_names'][28][3:5] == 'D1' + assert raw.info['ch_names'][0][3:5] == 'D1' assert_allclose( - mni_locs[28], [-0.0292, 0.0852, -0.0142], atol=allowed_dist_error) + mni_locs[0], [-0.0292, 0.0852, -0.0142], atol=allowed_dist_error) - assert raw.info['ch_names'][42][3:5] == 'D4' + assert raw.info['ch_names'][15][3:5] == 'D4' assert_allclose( - mni_locs[42], [-0.0739, -0.0756, -0.0075], atol=allowed_dist_error) + mni_locs[15], [-0.0739, -0.0756, -0.0075], atol=allowed_dist_error) # Old name aliases for backward compat assert 'fnirs_cw_amplitude' in raw @@ -552,12 +553,12 @@ def test_nirx_15_0(): tzinfo=dt.timezone.utc) # Test channel naming - assert raw.info['ch_names'][:12] == ["S10_D10 760", "S10_D10 850", - "S1_D1 760", "S1_D1 850", + assert raw.info['ch_names'][:12] == ["S1_D1 760", "S1_D1 850", "S2_D2 760", "S2_D2 850", "S3_D3 760", "S3_D3 850", "S4_D4 760", "S4_D4 850", - "S5_D5 760", "S5_D5 850"] + "S5_D5 760", "S5_D5 850", + "S6_D6 760", "S6_D6 850"] # Test info import assert raw.info['subject_info'] == {'birthday': (2004, 10, 27), @@ -575,13 +576,13 @@ def test_nirx_15_0(): head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) - assert raw.info['ch_names'][2][3:5] == 'D1' + assert raw.info['ch_names'][0][3:5] == 'D1' assert_allclose( - mni_locs[2], [0.0287, -0.1143, -0.0332], atol=allowed_dist_error) + mni_locs[0], [0.0287, -0.1143, -0.0332], atol=allowed_dist_error) - assert raw.info['ch_names'][17][3:5] == 'D8' + assert raw.info['ch_names'][15][3:5] == 'D8' assert_allclose( - mni_locs[17], [-0.0693, -0.0480, 0.0657], atol=allowed_dist_error) + mni_locs[15], [-0.0693, -0.0480, 0.0657], atol=allowed_dist_error) # Test distance between optodes matches values from allowed_distance_error = 0.0002 @@ -605,3 +606,24 @@ def test_nirx_standard(fname, boundary_decimal): """Test standard operations.""" _test_raw_reader(read_raw_nirx, fname=fname, boundary_decimal=boundary_decimal) # low fs + + +# Below are the native (on-disk) orders, which should be preserved +@requires_testing_data +@pytest.mark.parametrize('fname, want_order', [ + (fname_nirx_15_0, ['S1_D1', 'S2_D2', 'S3_D3', 'S4_D4', 'S5_D5', 'S6_D6', 'S7_D7', 'S8_D8', 'S9_D9', 'S10_D10']), # noqa: E501 + (fname_nirx_15_2, ['S1_D1', 'S1_D10', 'S2_D1', 'S2_D2', 'S3_D2', 'S3_D3', 'S4_D3', 'S4_D4', 'S5_D4', 'S5_D5', 'S6_D5', 'S6_D6', 'S7_D6', 'S7_D7', 'S8_D7', 'S8_D8', 'S9_D8', 'S9_D9', 'S10_D9', 'S10_D10', 'S11_D11', 'S11_D12', 'S12_D12', 'S12_D13', 'S13_D13', 'S13_D14', 'S14_D14', 'S14_D15', 'S15_D15', 'S15_D16', 'S16_D11', 'S16_D16']), # noqa: E501 + (fname_nirx_15_2_short, ['S1_D1', 'S1_D9', 'S2_D3', 'S2_D10', 'S3_D2', 'S3_D11', 'S4_D4', 'S4_D12', 'S5_D5', 'S5_D6', 'S5_D7', 'S5_D8', 'S5_D13']), # noqa: E501 + (fname_nirx_15_3_short, ['S1_D2', 'S1_D9', 'S2_D1', 'S2_D10', 'S3_D3', 'S3_D11', 'S4_D4', 'S4_D12', 'S5_D5', 'S5_D6', 'S5_D7', 'S5_D8', 'S5_D13']), # noqa: E501 + (nirsport1_wo_sat, ['S1_D4', 'S1_D5', 'S1_D6', 'S2_D5', 'S2_D6', 'S3_D5', 'S4_D1', 'S4_D3', 'S4_D4', 'S5_D1', 'S5_D2', 'S6_D1', 'S6_D3']), # noqa: E501 + (nirsport2, ['S1_D1', 'S1_D6', 'S1_D9', 'S2_D2', 'S2_D10', 'S3_D5', 'S3_D7', 'S3_D11', 'S4_D8', 'S4_D12', 'S5_D3', 'S5_D13', 'S6_D4', 'S6_D14', 'S7_D1', 'S7_D6', 'S7_D15', 'S8_D5', 'S8_D7', 'S8_D16']), # noqa: E501 + (nirsport2_2021_9, ['S1_D1', 'S1_D3', 'S2_D1', 'S2_D2', 'S2_D4', 'S3_D2', 'S3_D5', 'S4_D1', 'S4_D3', 'S4_D4', 'S4_D6', 'S5_D2', 'S5_D4', 'S5_D5', 'S5_D7', 'S6_D3', 'S6_D6', 'S7_D4', 'S7_D6', 'S7_D7', 'S8_D5', 'S8_D7']), # noqa: E501 +]) +def test_channel_order(fname, want_order): + """Test that logical channel order is preserved.""" + raw = read_raw_nirx(fname) + ch_names = raw.ch_names + prefixes = [ch_name.split()[0] for ch_name in ch_names] + assert prefixes[::2] == prefixes[1::2] + prefixes = prefixes[::2] + assert prefixes == want_order diff --git a/mne/io/snirf/_snirf.py b/mne/io/snirf/_snirf.py index 95beede6f8d..c6322b26ebb 100644 --- a/mne/io/snirf/_snirf.py +++ b/mne/io/snirf/_snirf.py @@ -15,7 +15,7 @@ from ..constants import FIFF from .._digitization import _make_dig_points from ...transforms import _frame_to_str, apply_trans -from ..nirx.nirx import _convert_fnirs_to_head +from ..nirx.nirx import _convert_fnirs_to_head, _nirs_sort_idx from ..._freesurfer import get_mni_fiducials @@ -411,8 +411,7 @@ def natural_keys(text): # MNE requires channels are paired as alternating wavelengths if len(_validate_nirs_info(self.info, throw_errors=False)) == 0: - sort_idx = np.argsort(self.ch_names) - self.pick(picks=sort_idx) + self.pick(picks=_nirs_sort_idx(self.info)) # Validate that the fNIRS info is correctly formatted _validate_nirs_info(self.info) diff --git a/mne/io/snirf/tests/test_snirf.py b/mne/io/snirf/tests/test_snirf.py index 938247c3ea2..5fb85e710f5 100644 --- a/mne/io/snirf/tests/test_snirf.py +++ b/mne/io/snirf/tests/test_snirf.py @@ -92,6 +92,24 @@ def test_snirf_gowerlabs(): assert raw.info['dig'][0]['coord_frame'] == FIFF.FIFFV_COORD_HEAD assert len(raw.ch_names) == 216 assert_allclose(raw.info['sfreq'], 10.0) + # we don't force them to be sorted according to a naive split + # (but we do force them to be interleaved, which is tested by beer_lambert + # above) + assert raw.ch_names != sorted(raw.ch_names) + # ... and this file does have a nice logical ordering already + assert raw.ch_names == sorted( + raw.ch_names, # use a key which is (source int, detector int) + key=lambda name: (int(name.split()[0].split('_')[0][1:]), + int(name.split()[0].split('_')[1][1:]))) + prefixes = [name.split()[0] for name in raw.ch_names] + # TODO: This is actually not the order on disk -- we reorder to ravel as + # S-D then freq, but gowerlabs order is freq then S-D. So hopefully soon + # we can change these lines to check that the first half of prefixes + # matches the second half of prefixes, rather than every-other matching the + # other every-other + assert prefixes[::2] == prefixes[1::2] + prefixes = prefixes[::2] + assert prefixes == ['S1_D1', 'S1_D2', 'S1_D3', 'S1_D4', 'S1_D5', 'S1_D6', 'S1_D7', 'S1_D8', 'S1_D9', 'S1_D10', 'S1_D11', 'S1_D12', 'S2_D1', 'S2_D2', 'S2_D3', 'S2_D4', 'S2_D5', 'S2_D6', 'S2_D7', 'S2_D8', 'S2_D9', 'S2_D10', 'S2_D11', 'S2_D12', 'S3_D1', 'S3_D2', 'S3_D3', 'S3_D4', 'S3_D5', 'S3_D6', 'S3_D7', 'S3_D8', 'S3_D9', 'S3_D10', 'S3_D11', 'S3_D12', 'S4_D1', 'S4_D2', 'S4_D3', 'S4_D4', 'S4_D5', 'S4_D6', 'S4_D7', 'S4_D8', 'S4_D9', 'S4_D10', 'S4_D11', 'S4_D12', 'S5_D1', 'S5_D2', 'S5_D3', 'S5_D4', 'S5_D5', 'S5_D6', 'S5_D7', 'S5_D8', 'S5_D9', 'S5_D10', 'S5_D11', 'S5_D12', 'S6_D1', 'S6_D2', 'S6_D3', 'S6_D4', 'S6_D5', 'S6_D6', 'S6_D7', 'S6_D8', 'S6_D9', 'S6_D10', 'S6_D11', 'S6_D12', 'S7_D1', 'S7_D2', 'S7_D3', 'S7_D4', 'S7_D5', 'S7_D6', 'S7_D7', 'S7_D8', 'S7_D9', 'S7_D10', 'S7_D11', 'S7_D12', 'S8_D1', 'S8_D2', 'S8_D3', 'S8_D4', 'S8_D5', 'S8_D6', 'S8_D7', 'S8_D8', 'S8_D9', 'S8_D10', 'S8_D11', 'S8_D12', 'S9_D1', 'S9_D2', 'S9_D3', 'S9_D4', 'S9_D5', 'S9_D6', 'S9_D7', 'S9_D8', 'S9_D9', 'S9_D10', 'S9_D11', 'S9_D12'] # noqa: E501 @requires_testing_data @@ -106,6 +124,7 @@ def test_snirf_basic(): # Test channel naming assert raw.info['ch_names'][:4] == ["S1_D1 760", "S1_D1 850", "S1_D9 760", "S1_D9 850"] + assert raw.info['ch_names'][24:26] == ["S5_D13 760", "S5_D13 850"] # Test frequency encoding assert raw.info['chs'][0]['loc'][9] == 760 @@ -206,9 +225,9 @@ def test_snirf_nirsport2(): assert_almost_equal(raw.info['sfreq'], 7.6, decimal=1) # Test channel naming - assert raw.info['ch_names'][:4] == ['S10_D3 760', 'S10_D3 850', - 'S10_D9 760', 'S10_D9 850'] - assert raw.info['ch_names'][24:26] == ['S15_D11 760', 'S15_D11 850'] + assert raw.info['ch_names'][:4] == ['S1_D1 760', 'S1_D1 850', + 'S1_D3 760', 'S1_D3 850'] + assert raw.info['ch_names'][24:26] == ['S6_D4 760', 'S6_D4 850'] # Test frequency encoding assert raw.info['chs'][0]['loc'][9] == 760 @@ -246,7 +265,7 @@ def test_snirf_nirsport2_w_positions(): # Test channel naming assert raw.info['ch_names'][:4] == ['S1_D1 760', 'S1_D1 850', 'S1_D6 760', 'S1_D6 850'] - assert raw.info['ch_names'][24:26] == ['S6_D14 760', 'S6_D14 850'] + assert raw.info['ch_names'][24:26] == ['S6_D4 760', 'S6_D4 850'] # Test frequency encoding assert raw.info['chs'][0]['loc'][9] == 760 @@ -282,6 +301,10 @@ def test_snirf_nirsport2_w_positions(): assert_allclose( mni_locs[2], [-0.0841, -0.0138, 0.0248], atol=allowed_dist_error) + assert raw.info['ch_names'][34][3:5] == 'D5' + assert_allclose( + mni_locs[34], [0.0845, -0.0451, -0.0123], atol=allowed_dist_error) + # Test location of sensors # The locations of sensors can be seen in the second # figure on this page... diff --git a/mne/preprocessing/nirs/tests/test_beer_lambert_law.py b/mne/preprocessing/nirs/tests/test_beer_lambert_law.py index 48115e3adc1..c6fde250e77 100644 --- a/mne/preprocessing/nirs/tests/test_beer_lambert_law.py +++ b/mne/preprocessing/nirs/tests/test_beer_lambert_law.py @@ -88,21 +88,12 @@ def test_beer_lambert_v_matlab(): 'nirx_15_0_recording_bl.mat') matlab_data = read_mat(matlab_fname) - matlab_names = ["_"] * len(raw.ch_names) - for idx in range(len(raw.ch_names)): - matlab_names[idx] = ("S" + str(int(matlab_data['sources'][idx])) + - "_D" + str(int(matlab_data['detectors'][idx])) + - " " + matlab_data['type'][idx]) - matlab_to_mne = np.argsort(matlab_names) - for idx in range(raw.get_data().shape[0]): - matlab_idx = matlab_to_mne[idx] - - mean_error = np.mean(matlab_data['data'][:, matlab_idx] - + mean_error = np.mean(matlab_data['data'][:, idx] - raw._data[idx]) assert mean_error < 0.1 - matlab_name = ("S" + str(int(matlab_data['sources'][matlab_idx])) + - "_D" + str(int(matlab_data['detectors'][matlab_idx])) + - " " + matlab_data['type'][matlab_idx]) + matlab_name = ("S" + str(int(matlab_data['sources'][idx])) + + "_D" + str(int(matlab_data['detectors'][idx])) + + " " + matlab_data['type'][idx]) assert raw.info['ch_names'][idx] == matlab_name diff --git a/mne/preprocessing/nirs/tests/test_temporal_derivative_distribution_repair.py b/mne/preprocessing/nirs/tests/test_temporal_derivative_distribution_repair.py index 546eae9e2ab..59f2547f079 100644 --- a/mne/preprocessing/nirs/tests/test_temporal_derivative_distribution_repair.py +++ b/mne/preprocessing/nirs/tests/test_temporal_derivative_distribution_repair.py @@ -28,7 +28,7 @@ def test_temporal_derivative_distribution_repair(fname, tmp_path): # Add a baseline shift artifact about half way through data max_shift = np.max(np.diff(raw._data[0])) shift_amp = 5 * max_shift - raw._data[0, 0:30] = raw._data[0, 0:30] - (1.1 * shift_amp) + raw._data[0, 0:30] = raw._data[0, 0:30] - shift_amp # make one channel zero std raw._data[1] = 0. raw._data[2] = 1. diff --git a/tutorials/io/30_reading_fnirs_data.py b/tutorials/io/30_reading_fnirs_data.py index 15f63e4a678..a32736aa725 100644 --- a/tutorials/io/30_reading_fnirs_data.py +++ b/tutorials/io/30_reading_fnirs_data.py @@ -25,6 +25,14 @@ Manual modification of channel names and metadata is not recommended. +.. note:: To provide a consistent interface across different measurement + devices and file types, MNE-Python uses a standard ordering of + channels. MNE-Python internally orders channels by ascending source + number. When there are multiple channels with the same source number, + they are ordered by ascending detector number. The ordering of + channels is done automatically when data is imported. Therefore, + the ordering of channels within MNE-Python may be different to what + was provided by the hardware vendor. .. _import-snirf: @@ -156,7 +164,7 @@ naming and ordering of channels, the type and scaling of data, and specification of sensor positions varies between each vendor). You will likely have to adapt this depending on the system from which your CSV originated. -""" # noqa:E501 +""" # %%