def test_long_extraction(): fnirs_data_folder = mne.datasets.fnirs_motor.data_path() fnirs_raw_dir = os.path.join(fnirs_data_folder, 'Participant-1') raw_intensity = mne.io.read_raw_nirx(fnirs_raw_dir).load_data() long_chans = get_long_channels(raw_intensity) original_num_channels = len(raw_intensity.ch_names) assert original_num_channels == 56 long_num_channels = len(long_chans.ch_names) assert long_num_channels == 56 - 16 new_lens = source_detector_distances(long_chans.info) assert np.min(new_lens) >= 0.01 # Now test for non standard short length long_chans = get_long_channels(raw_intensity, min_dist=0.022) long_num_channels = len(long_chans.ch_names) assert long_num_channels > 16 # There are 8 SDs in this set * hbo/hbr new_lens = source_detector_distances(long_chans.info) assert np.max(new_lens) >= 0.022 # Check that we dont run on other types, eg eeg. raw_intensity.pick(picks=range(2)) raw_intensity.set_channel_types({'S1_D1 760': 'eeg', 'S1_D1 850': 'eeg'}, verbose='error') with pytest.raises(RuntimeError, match='NIRS signals only'): _ = get_long_channels(raw_intensity)
def test_nirx_15_0(): """Test reading NIRX files.""" raw = read_raw_nirx(fname_nirx_15_0, preload=True) # Test data import assert raw._data.shape == (20, 92) assert raw.info['sfreq'] == 6.25 assert raw.info['meas_date'] == dt.datetime(2019, 10, 27, 13, 53, 34, 209000, tzinfo=dt.timezone.utc) # Test channel naming 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", "S6_D6 760", "S6_D6 850" ] # Test info import assert raw.info['subject_info'] == { 'birthday': (2004, 10, 27), 'first_name': 'NIRX', 'last_name': 'Test', 'sex': FIFF.FIFFV_SUBJ_SEX_UNKNOWN, 'his_id': "NIRX_Test" } # Test trigger events assert_array_equal(raw.annotations.description, ['1.0', '2.0', '2.0']) # Test location of detectors allowed_dist_error = 0.0002 locs = [ch['loc'][6:9] for ch in raw.info['chs']] head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) assert raw.info['ch_names'][0][3:5] == 'D1' assert_allclose(mni_locs[0], [0.0287, -0.1143, -0.0332], atol=allowed_dist_error) assert raw.info['ch_names'][15][3:5] == 'D8' assert_allclose(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 assert_allclose(source_detector_distances( raw.copy().pick("S1_D1 760").info), [0.0300], atol=allowed_distance_error) assert_allclose(source_detector_distances( raw.copy().pick("S7_D7 760").info), [0.0392], atol=allowed_distance_error)
def get_long_channels(raw, min_dist=0.01): """ Return channels with a long source detector separation. Parameters ---------- raw : instance of Raw The haemoglobin data. min_dist : number Minimum distance of returned channel. Returns ------- raw : instance of Raw Raw instance with only long channels. """ long_chans = raw.copy().load_data() _validate_type(long_chans, BaseRaw, 'raw') picks = mne.pick_types(long_chans.info, meg=False, eeg=False, fnirs=True) if not len(picks): raise RuntimeError('Short channel extraction for NIRS signals only.') dists = source_detector_distances(long_chans.info, picks=picks) long_chans.pick(picks[dists > min_dist]) return long_chans
def get_short_channels(raw, max_dist=0.01): """ Return channels with a short source-detector separation. Parameters ---------- raw : instance of Raw Raw instance containing fNIRS data. max_dist : number Maximum distance of returned channels (m). Returns ------- raw : instance of Raw Raw instance with only short channels. """ short_chans = raw.copy().load_data() _validate_type(short_chans, BaseRaw, 'raw') picks = mne.pick_types(short_chans.info, meg=False, eeg=False, fnirs=True, exclude=[]) if not len(picks): raise RuntimeError('Short channel extraction for NIRS signals only.') dists = source_detector_distances(short_chans.info, picks=picks) short_chans.pick(picks[dists < max_dist]) return short_chans
def short_channel_regression(raw, max_dist=0.01): """ Systemic correction regression based on nearest short channel. Method as described by NIRx and based on :footcite:`fabbri2004optical`, :footcite:`saager2005direct`, and :footcite:`scholkmann2014measuring`. Parameters ---------- raw : instance of Raw Raw instance containing optical density data. max_dist : number Channels less than this distance are considered short (m). Returns ------- raw : instance of Raw The modified raw instance. References ---------- .. footbibliography:: """ raw = raw.copy().load_data() _validate_type(raw, BaseRaw, 'raw') picks_od = pick_types(raw.info, fnirs='fnirs_od') if len(picks_od) == 0: raise RuntimeError('Data must be optical density.') distances = source_detector_distances(raw.info) picks_short = picks_od[distances[picks_od] < max_dist] picks_long = picks_od[distances[picks_od] > max_dist] if len(picks_short) == 0: raise RuntimeError('No short channels present.') if len(picks_long) == 0: raise RuntimeError('No long channels present.') for pick in picks_long: short_idx = _find_nearest_short(raw, pick, picks_short) A_l = raw.get_data(pick).ravel() A_s = raw.get_data(short_idx).ravel() # Eqn 27 Scholkmann et al 2014 alfa = np.dot(A_s, A_l) / np.dot(A_s, A_s) # Eqn 26 Scholkmann et al 2014 raw._data[pick] = A_l - alfa * A_s return raw
def test_long_extraction(): fnirs_data_folder = mne.datasets.fnirs_motor.data_path() fnirs_raw_dir = os.path.join(fnirs_data_folder, 'Participant-1') raw_intensity = mne.io.read_raw_nirx(fnirs_raw_dir).load_data() long_chans = get_long_channels(raw_intensity) original_num_channels = len(raw_intensity.ch_names) assert original_num_channels == 56 long_num_channels = len(long_chans.ch_names) assert long_num_channels == 56 - 16 new_lens = source_detector_distances(long_chans.info) assert np.min(new_lens) >= 0.01 # Now test for non standard short length long_chans = get_long_channels(raw_intensity, min_dist=0.022) long_num_channels = len(long_chans.ch_names) assert long_num_channels > 16 # There are 8 SDs in this set * hbo/hbr new_lens = source_detector_distances(long_chans.info) assert np.max(new_lens) >= 0.022
def test_nirx_15_0(): """Test reading NIRX files.""" raw = read_raw_nirx(fname_nirx_15_0, preload=True) # Test data import assert raw._data.shape == (20, 92) assert raw.info['sfreq'] == 6.25 # Test channel naming 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", "S6_D6 760", "S6_D6 850" ] # Test info import assert raw.info['subject_info'] == { 'first_name': 'NIRX', 'last_name': 'Test', 'sex': '0' } # Test trigger events assert_array_equal(raw.annotations.description, ['1.0', '2.0', '2.0']) # Test location of detectors allowed_dist_error = 0.0002 locs = [ch['loc'][6:9] for ch in raw.info['chs']] head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) assert raw.info['ch_names'][0][3:5] == 'D1' assert_allclose(mni_locs[0], [0.0287, -0.1143, -0.0332], atol=allowed_dist_error) assert raw.info['ch_names'][15][3:5] == 'D8' assert_allclose(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 distances = source_detector_distances(raw.info) assert_allclose(distances[::2], [ 0.0301, 0.0315, 0.0343, 0.0368, 0.0408, 0.0399, 0.0393, 0.0367, 0.0336, 0.0447 ], atol=allowed_distance_error)
def test_snirf_nirsport2_w_positions(): """Test reading SNIRF files with known positions.""" raw = read_raw_snirf(nirx_nirsport2_103_2, preload=True, optode_frame="mri") # Test data import assert raw._data.shape == (40, 128) assert_almost_equal(raw.info['sfreq'], 10.2, decimal=1) # 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_D4 760', 'S6_D4 850'] # Test frequency encoding assert raw.info['chs'][0]['loc'][9] == 760 assert raw.info['chs'][1]['loc'][9] == 850 assert sum(short_channels(raw.info)) == 16 # Test distance between optodes matches values from # nirsite https://github.com/mne-tools/mne-testing-data/pull/86 # figure 3 allowed_distance_error = 0.005 distances = source_detector_distances(raw.info) assert_allclose(distances[::2][:14], [ 0.0304, 0.0411, 0.008, 0.0400, 0.008, 0.0310, 0.0411, 0.008, 0.0299, 0.008, 0.0370, 0.008, 0.0404, 0.008 ], atol=allowed_distance_error) # Test location of detectors # The locations of detectors can be seen in the first # figure on this page... # https://github.com/mne-tools/mne-testing-data/pull/86 allowed_dist_error = 0.0002 locs = [ch['loc'][6:9] for ch in raw.info['chs']] head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) assert raw.info['ch_names'][0][3:5] == 'D1' assert_allclose(mni_locs[0], [-0.0841, -0.0464, -0.0129], atol=allowed_dist_error) assert raw.info['ch_names'][2][3:5] == 'D6' 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... # https://github.com/mne-tools/mne-testing-data/pull/86 allowed_dist_error = 0.0002 locs = [ch['loc'][3:6] for ch in raw.info['chs']] head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) assert raw.info['ch_names'][0][:2] == 'S1' assert_allclose(mni_locs[0], [-0.0848, -0.0162, -0.0163], atol=allowed_dist_error) assert raw.info['ch_names'][9][:2] == 'S2' assert_allclose(mni_locs[9], [-0.0, -0.1195, 0.0142], atol=allowed_dist_error) assert raw.info['ch_names'][34][:2] == 'S8' assert_allclose(mni_locs[34], [0.0828, -0.046, 0.0285], atol=allowed_dist_error) mon = raw.get_montage() assert len(mon.dig) == 43
def test_nirsport_v2(): """Test NIRSport2 file.""" raw = read_raw_nirx(nirsport2, preload=True) assert raw._data.shape == (40, 128) # Test distance between optodes matches values from # nirsite https://github.com/mne-tools/mne-testing-data/pull/86 # figure 3 allowed_distance_error = 0.005 distances = source_detector_distances(raw.info) assert_allclose(distances[::2][:14], [ 0.0304, 0.0411, 0.008, 0.0400, 0.008, 0.0310, 0.0411, 0.008, 0.0299, 0.008, 0.0370, 0.008, 0.0404, 0.008 ], atol=allowed_distance_error) # Test location of detectors # The locations of detectors can be seen in the first # figure on this page... # https://github.com/mne-tools/mne-testing-data/pull/86 allowed_dist_error = 0.0002 locs = [ch['loc'][6:9] for ch in raw.info['chs']] head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) assert raw.info['ch_names'][0][3:5] == 'D1' assert_allclose(mni_locs[0], [-0.0841, -0.0464, -0.0129], atol=allowed_dist_error) assert raw.info['ch_names'][2][3:5] == 'D6' 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... # https://github.com/mne-tools/mne-testing-data/pull/86 locs = [ch['loc'][3:6] for ch in raw.info['chs']] head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) assert raw.info['ch_names'][0][:2] == 'S1' assert_allclose(mni_locs[0], [-0.0848, -0.0162, -0.0163], atol=allowed_dist_error) assert raw.info['ch_names'][9][:2] == 'S2' assert_allclose(mni_locs[9], [-0.0, -0.1195, 0.0142], atol=allowed_dist_error) assert raw.info['ch_names'][34][:2] == 'S8' assert_allclose(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' assert raw.annotations.description[2] == '6.0' # Lose tolerance as I am eyeballing the time differences on screen assert_allclose(np.diff(raw.annotations.onset), [2.3, 3.1], atol=0.1) mon = raw.get_montage() assert len(mon.dig) == 43
def test_nirx_15_3_short(): """Test reading NIRX files.""" raw = read_raw_nirx(fname_nirx_15_3_short, preload=True) # Test data import assert raw._data.shape == (26, 220) assert raw.info['sfreq'] == 12.5 # 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_D13 760", "S5_D13 850"] # Test frequency encoding assert raw.info['chs'][0]['loc'][9] == 760 assert raw.info['chs'][1]['loc'][9] == 850 # Test info import assert raw.info['subject_info'] == dict(birthday=(2020, 8, 18), sex=0, first_name="testMontage\\0A" "TestMontage", his_id="testMontage\\0A" "TestMontage") # Test distance between optodes matches values from # https://github.com/mne-tools/mne-testing-data/pull/72 allowed_distance_error = 0.001 distances = source_detector_distances(raw.info) assert_allclose(distances[::2], [ 0.0304, 0.0078, 0.0310, 0.0086, 0.0416, 0.0072, 0.0389, 0.0075, 0.0558, 0.0562, 0.0561, 0.0565, 0.0077 ], atol=allowed_distance_error) # Test which channels are 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, 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) assert_array_equal(is_short[:3:2], [True, True]) # Test trigger events assert_array_equal(raw.annotations.description, ['4.0', '2.0', '1.0']) # Test location of detectors # The locations of detectors can be seen in the first # figure on this page... # https://github.com/mne-tools/mne-testing-data/pull/72 # And have been manually copied below allowed_dist_error = 0.0002 locs = [ch['loc'][6:9] for ch in raw.info['chs']] head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) assert raw.info['ch_names'][0][3:5] == 'D2' assert_allclose(mni_locs[0], [-0.0841, -0.0464, -0.0129], atol=allowed_dist_error) assert raw.info['ch_names'][4][3:5] == 'D1' assert_allclose(mni_locs[4], [0.0846, -0.0142, -0.0156], atol=allowed_dist_error) assert raw.info['ch_names'][8][3:5] == 'D3' assert_allclose(mni_locs[8], [0.0207, -0.1062, 0.0484], atol=allowed_dist_error) assert raw.info['ch_names'][12][3:5] == 'D4' assert_allclose(mni_locs[12], [-0.0196, 0.0821, 0.0275], atol=allowed_dist_error) assert raw.info['ch_names'][16][3:5] == 'D5' assert_allclose(mni_locs[16], [-0.0360, 0.0276, 0.0778], atol=allowed_dist_error) assert raw.info['ch_names'][19][3:5] == 'D6' assert_allclose(mni_locs[19], [0.0388, -0.0477, 0.0932], atol=allowed_dist_error) assert raw.info['ch_names'][21][3:5] == 'D7' assert_allclose(mni_locs[21], [-0.0394, -0.0483, 0.0928], atol=allowed_dist_error)
def test_nirx_15_2_short(): """Test reading NIRX files.""" raw = read_raw_nirx(fname_nirx_15_2_short, preload=True) # Test data import assert raw._data.shape == (26, 145) assert raw.info['sfreq'] == 12.5 assert raw.info['meas_date'] == dt.datetime(2019, 8, 23, 7, 37, 4, 540000, tzinfo=dt.timezone.utc) # 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 assert raw.info['chs'][1]['loc'][9] == 850 # Test info import assert raw.info['subject_info'] == dict(sex=1, first_name="MNE", middle_name="Test", last_name="Recording", birthday=(2014, 8, 23), his_id="MNE_Test_Recording") # Test distance between optodes matches values from # nirsite https://github.com/mne-tools/mne-testing-data/pull/51 # step 4 figure 2 allowed_distance_error = 0.0002 distances = source_detector_distances(raw.info) assert_allclose(distances[::2], [ 0.0304, 0.0078, 0.0310, 0.0086, 0.0416, 0.0072, 0.0389, 0.0075, 0.0558, 0.0562, 0.0561, 0.0565, 0.0077 ], atol=allowed_distance_error) # Test which channels are 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, 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) assert_array_equal(is_short[:3:2], [True, True]) # Test trigger events assert_array_equal(raw.annotations.description, ['3.0', '2.0', '1.0']) # Test location of detectors # The locations of detectors can be seen in the first # figure on this page... # https://github.com/mne-tools/mne-testing-data/pull/51 # And have been manually copied below # These values were reported in mm, but according to this page... # https://mne.tools/stable/auto_tutorials/intro/plot_40_sensor_locations.html # 3d locations should be specified in meters, so that's what's tested below # Detector locations are stored in the third three loc values allowed_dist_error = 0.0002 locs = [ch['loc'][6:9] for ch in raw.info['chs']] head_mri_t, _ = _get_trans('fsaverage', 'head', 'mri') mni_locs = apply_trans(head_mri_t, locs) assert raw.info['ch_names'][0][3:5] == 'D1' assert_allclose(mni_locs[0], [-0.0841, -0.0464, -0.0129], atol=allowed_dist_error) assert raw.info['ch_names'][4][3:5] == 'D3' assert_allclose(mni_locs[4], [0.0846, -0.0142, -0.0156], atol=allowed_dist_error) assert raw.info['ch_names'][8][3:5] == 'D2' assert_allclose(mni_locs[8], [0.0207, -0.1062, 0.0484], atol=allowed_dist_error) assert raw.info['ch_names'][12][3:5] == 'D4' assert_allclose(mni_locs[12], [-0.0196, 0.0821, 0.0275], atol=allowed_dist_error) assert raw.info['ch_names'][16][3:5] == 'D5' assert_allclose(mni_locs[16], [-0.0360, 0.0276, 0.0778], atol=allowed_dist_error) assert raw.info['ch_names'][19][3:5] == 'D6' assert_allclose(mni_locs[19], [0.0352, 0.0283, 0.0780], atol=allowed_dist_error) assert raw.info['ch_names'][21][3:5] == 'D7' assert_allclose(mni_locs[21], [0.0388, -0.0477, 0.0932], atol=allowed_dist_error)
def short_channel_regression(raw, max_dist=0.01): """ Short channel regression based on nearest channel. Fabbri, Francesco, et al. "Optical measurements of absorption changes in two-layered diffusive media." Physics in Medicine & Biology 49.7 (2004): 1183. Saager, Rolf B., and Andrew J. Berger. "Direct characterization and removal of interfering absorption trends in two-layer turbid media." JOSA A 22.9 (2005): 1874-1882. Scholkmann, Felix, Andreas Jaakko Metz, and Martin Wolf. "Measuring tissue hemodynamics and oxygenation by continuous-wave functional near-infrared spectroscopy—how robust are the different calculation methods against movement artifacts?." Physiological measurement 35.4 (2014): 717. Parameters ---------- raw : instance of Raw Haemoglobin data. max_dist : number Channels less than this distance are considered short (m). Returns ------- raw : instance of Raw The modified raw instance. """ raw = raw.copy().load_data() _validate_type(raw, BaseRaw, 'raw') picks_od = pick_types(raw.info, fnirs='fnirs_od') if len(picks_od) == 0: raise RuntimeError('Data must be optical density.') distances = source_detector_distances(raw.info) picks_short = picks_od[distances[picks_od] < max_dist] picks_long = picks_od[distances[picks_od] > max_dist] if len(picks_short) == 0: raise RuntimeError('No short channels present.') if len(picks_long) == 0: raise RuntimeError('No long channels present.') for pick in picks_long: short_idx = _find_nearest_short(raw, pick, picks_short) A_l = raw.get_data(pick).ravel() A_s = raw.get_data(short_idx).ravel() # Eqn 27 Scholkmann et al 2014 alfa = np.dot(A_s, A_l) / np.dot(A_s, A_s) # Eqn 26 Scholkmann et al 2014 raw._data[pick] = A_l - alfa * A_s return raw