def test_two_pd_alignment(): """Test spliting photodiode events into two and adding.""" out_dir = _TempDir() raw, _, events, _ = pd_parser.simulate_pd_data(prop_corrupted=0.) fname = op.join(out_dir, 'test-raw.fif') raw.save(fname) events2 = events[::2] events3 = events[1:][::2] # make behavior data np.random.seed(12) beh_events2 = events2[:, 0].astype(float) / raw.info['sfreq'] offsets2 = np.random.random(len(beh_events2)) * 0.05 - 0.025 beh_events2 += offsets2 # make next one beh_events3 = events3[:, 0].astype(float) / raw.info['sfreq'] offsets3 = np.random.random(len(beh_events3)) * 0.05 - 0.025 beh_events3 += offsets3 n_na = abs(len(beh_events2) - len(beh_events3)) if len(beh_events2) > len(beh_events3): beh_events3 = list(beh_events3) + ['n/a'] * n_na elif len(beh_events3) > len(beh_events2): beh_events2 = list(beh_events2) + ['n/a'] * n_na beh = dict(trial=np.arange(len(beh_events2)), fix_onset_time=beh_events2, response_onset_time=beh_events3) behf = op.join(out_dir, 'behf-test.tsv') _to_tsv(behf, beh) pd_parser.parse_pd(fname, pd_event_name='Fixation', beh=beh, pd_ch_names=['pd'], beh_key='fix_onset_time', zscore=20, exclude_shift=0.05) pd_parser.parse_pd(fname, pd_event_name='Response', beh=beh, pd_ch_names=['pd'], beh_key='response_onset_time', zscore=20, add_events=True, exclude_shift=0.05) raw = _read_raw(fname) annot, pd_ch_names, beh2 = _load_data(raw) raw.set_annotations(annot) events4, event_id = mne.events_from_annotations(raw) np.testing.assert_array_equal(events4[events4[:, 2] == 1, 0], events2[:, 0]) np.testing.assert_array_equal(events4[events4[:, 2] == 2, 0], events3[:, 0]) assert pd_ch_names == ['pd'] np.testing.assert_array_equal(beh2['pd_parser_sample'], events2[:, 0])
def make_raw(out_dir): np.random.seed(99) raw, beh, events, corrupted = pd_parser.simulate_pd_data(seed=99) raw2 = mne.io.RawArray( np.random.random((3, raw._data.shape[1])), mne.create_info([f'ch{i}' for i in range(3)], raw.info['sfreq'], ['eeg'] * 3)) raw.add_channels([raw2]) offsets = np.random.random(beh['time'].size) * 0.05 - 0.025 beh['fix_onset_time'] = beh['time'] + offsets response_times = list(np.random.random(beh['time'].size)) for i in np.random.choice(range(beh['time'].size), 3): response_times[i] = 'n/a' beh['fix_duration'] = [0.6] * beh['time'].size beh['go_time'] = np.random.random(beh['time'].size) beh['response_time'] = response_times fname = op.join(out_dir, 'test-raw.fif') raw.save(fname) behf = op.join(out_dir, 'behf-test.tsv') _to_tsv(behf, beh) return fname, behf, corrupted
def test_resync(): """Test when event resynchronicazationu using ``resync`` is needed.""" np.random.seed(12) raw, beh, events, corrupted_indices = \ pd_parser.simulate_pd_data(prop_corrupted=0.) pd = raw._data[0] exclude_shift_i = np.round(raw.info['sfreq'] * exclude_shift).astype(int) candidates = _find_pd_candidates(pd, max_len=max_len, baseline=baseline, zscore=zscore, max_flip_i=max_flip_i, sfreq=raw.info['sfreq'])[0] beh_events = beh['time'] * raw.info['sfreq'] offsets = (2 * resync * np.random.random(beh_events.size) - 1) * raw.info['sfreq'] beh_events += offsets beh_events -= beh_events[0] beh_events_adjusted, alignment, best_events = _find_best_alignment( beh_events, candidates, exclude_shift, resync, raw.info['sfreq']) errors = beh_events_adjusted - best_events + alignment resync_exclusions = np.where(abs(errors) > exclude_shift_i)[0] idx = resync_exclusions[0] correct = (best_events[idx], f'{idx}\nrecovered (not excluded)') assert len(resync_exclusions) > 0 # test exclude ambiguous pd_events = _exclude_ambiguous_events(beh_events_adjusted, alignment, best_events, pd, candidates, exclude_shift, max_len, raw.info['sfreq'], recover, zscore) assert np.isnan(pd_events[resync_exclusions]).all() assert np.isnan(pd_events[np.isnan(best_events)]).all() with mock.patch('builtins.input', return_value='y'): found = _recover_event(idx, pd, beh_events_adjusted[idx] + alignment, 2 * resync, zscore, max_len, raw.info['sfreq']) assert abs(found[0] - correct[0]) < 2 assert found[1] == correct[1]
import pd_parser from pd_parser.parse_pd import _load_data import matplotlib.pyplot as plt import matplotlib.cm as cm out_dir = _TempDir() # simulate photodiode data np.random.seed(29) n_events = 300 # let's make our photodiode events on random uniform from 0.5 to 1 second n_secs_on = np.random.random(n_events) * 0.5 + 0.5 prop_corrupted = 0.01 raw, beh, events, corrupted_indices = \ pd_parser.simulate_pd_data(n_events=n_events, n_secs_on=n_secs_on, prop_corrupted=prop_corrupted) # make fake electrophysiology data info = mne.create_info(['ch1', 'ch2', 'ch3'], raw.info['sfreq'], ['seeg'] * 3) raw2 = mne.io.RawArray(np.random.random((3, raw.times.size)) * 1e-6, info) raw2.info['lowpass'] = raw.info['lowpass'] # these must match to combine raw.add_channels([raw2]) # bids needs these data fields raw.info['dig'] = None raw.info['line_freq'] = 60 # add some offsets to the behavior so it's a bit more realistic offsets = np.random.randn(n_events) * 0.01 beh['time'] = np.array(beh['time']) + offsets # save to disk as required by ``pd-parser``
import numpy as np import mne from mne.utils import _TempDir import pd_parser from pd_parser.parse_pd import _read_raw, _to_tsv out_dir = _TempDir() print(f'After running this example, you can find the data here: {out_dir}') # simulate photodiode data n_events = 300 prop_corrupted = 0.01 raw, beh, events, corrupted_indices = \ pd_parser.simulate_pd_data(n_events=n_events, prop_corrupted=prop_corrupted) # make fake electrophysiology data info = mne.create_info(['ch1', 'ch2', 'ch3'], raw.info['sfreq'], ['seeg'] * 3) raw2 = mne.io.RawArray(np.random.random((3, raw.times.size)) * 1e-6, info) raw2.info['lowpass'] = raw.info['lowpass'] # these must match to combine raw.add_channels([raw2]) # bids needs these data fields raw.info['dig'] = None raw.info['line_freq'] = 60 fname = op.join(out_dir, 'sub-1_task-mytask_raw.fif') raw.save(fname) # roundtrip so that raw is properly loaded from disk and has a filename
def test_inputs(): """Test that inputs for functions raise necessary errors.""" out_dir = _TempDir() # test tsv beh = dict(test=[1, 2], test2=[2, 1]) _to_tsv(op.join(out_dir, 'test.tsv'), beh) assert beh == _read_tsv(op.join(out_dir, 'test.tsv')) with pytest.raises(ValueError, match='Unable to read'): _read_tsv('test.foo') with pytest.raises(ValueError, match='Error in reading tsv'): with open(op.join(out_dir, 'test.tsv'), 'w') as _: pass _read_tsv(op.join(out_dir, 'test.tsv')) with pytest.raises(ValueError, match='contains no data'): with open(op.join(out_dir, 'test.tsv'), 'w') as f: f.write('test') _read_tsv(op.join(out_dir, 'test.tsv')) with pytest.raises(ValueError, match='different lengths'): with open(op.join(out_dir, 'test.tsv'), 'w') as f: f.write('test\ttest2\n1\t1\n1') _read_tsv(op.join(out_dir, 'test.tsv')) with pytest.raises(ValueError, match='Empty data file, no keys'): _to_tsv(op.join(out_dir, 'test.tsv'), dict()) with pytest.raises(ValueError, match='Unable to write'): _to_tsv('foo.bar', dict(test=1)) # test read raw, beh, events, corrupted_indices = pd_parser.simulate_pd_data() with pytest.raises(ValueError, match='must be loaded from disk'): _read_raw(raw, preload=True) raw.save(op.join(out_dir, 'test-raw.fif'), overwrite=True) with pytest.raises(ValueError, match='not recognized'): _read_raw('foo.bar') raw2 = _read_raw(op.join(out_dir, 'test-raw.fif'), preload=True) np.testing.assert_array_almost_equal(raw._data, raw2._data, decimal=3) # test load beh with pytest.raises(ValueError, match='not in the columns'): _load_beh(op.join(basepath, 'pd_events.tsv'), 'foo') # test get pd data with pytest.raises(ValueError, match='in raw channel names'): _get_data(raw, ['foo']) with pytest.raises(ValueError, match='in raw channel names'): _get_channel_data(raw, ['foo']) with pytest.raises(ValueError, match='baseline must be between 0 and 1'): pd_parser.parse_pd(raw, beh=beh, baseline=2) with pytest.raises(FileNotFoundError, match='fname does not exist'): _load_data('bar/foo.fif') with pytest.raises(ValueError, match='pd-parser data not found'): raw.save(op.join(out_dir, 'foo.fif')) _load_data(op.join(out_dir, 'foo.fif')) # test i/o raw3 = _read_raw(op.join(out_dir, 'test-raw.fif')) _save_data(raw3, events=np.arange(10), event_id='Fixation', ch_names=['pd'], beh=beh, add_events=False) with pytest.raises(ValueError, match='`pd_parser_sample` is not allowed'): _save_data(raw3, events=events, event_id='Fixation', ch_names=['pd'], beh=beh, add_events=False) annot, pd_ch_names, beh2 = _load_data(raw3) raw.set_annotations(annot) events2, event_id = mne.events_from_annotations(raw) np.testing.assert_array_equal(events2[:, 0], np.arange(10)) assert event_id == {'Fixation': 1} assert pd_ch_names == ['pd'] np.testing.assert_array_equal(beh2['time'], beh['time']) np.testing.assert_array_equal(beh2['pd_parser_sample'], np.arange(10)) # check overwrite behf = op.join(out_dir, 'behf-test.tsv') _to_tsv(behf, beh) with pytest.raises(ValueError, match='directory already exists'): pd_parser.parse_pd(raw3, beh=behf) pd_parser.parse_pd(raw3, beh=None, pd_ch_names=['pd'], overwrite=True) annot, pd_ch_names, beh = _load_data(raw3) raw3.set_annotations(annot) events2, _ = mne.events_from_annotations(raw3) assert all([event in events2[:, 0] for event in events[:, 0]]) assert pd_ch_names == ['pd'] assert beh is None # test overwrite raw = _read_raw(op.join(out_dir, 'test-raw.fif')) with pytest.raises(ValueError, match='data directory already exists'): _check_overwrite(raw, add_events=False, overwrite=False)
def test_core(): """Test the core functions of aligning photodiode events.""" np.random.seed(121) raw, beh, events, corrupted_indices = pd_parser.simulate_pd_data(seed=1211) pd = raw._data[0] # test find pd candidates max_len = 1.5 exclude_shift_i = np.round(raw.info['sfreq'] * exclude_shift).astype(int) max_len_i = np.round(raw.info['sfreq'] * max_len).astype(int) baseline_i = np.round(max_len_i * baseline / 2).astype(int) resync_i = np.round(raw.info['sfreq'] * resync).astype(int) pd_diff = np.diff(pd) pd_diff -= np.median(pd_diff) median_std = np.median([ np.std(pd_diff[i - baseline_i:i]) for i in range(baseline_i, len(pd_diff) - baseline_i, baseline_i) ]) assert _check_if_pd_event(pd_diff, events[0, 0] - 1, max_len_i, zscore, max_flip_i, median_std) == \ ('up', events[0, 0], events[0, 0] + raw.info['sfreq']) # one sec event assert _check_if_pd_event(pd, baseline_i, max_len_i, zscore, max_flip_i, median_std) == \ (None, None, None) candidates = _find_pd_candidates(pd, max_len=max_len, baseline=baseline, zscore=zscore, max_flip_i=max_flip_i, sfreq=raw.info['sfreq'])[0] candidates_set = set(candidates) assert all([event in candidates for event in events[:, 0]]) # test pd event dist assert np.isnan( _event_dist(len(raw) + 10, candidates_set, len(raw), exclude_shift_i)).all() assert _event_dist(events[2, 0] + 10, candidates_set, len(raw), exclude_shift_i) == (10, events[2, 0]) assert _event_dist(events[2, 0] - 10, candidates_set, len(raw), exclude_shift_i) == (-10, events[2, 0]) # test find best alignment beh_events = beh['time'][2:] * raw.info['sfreq'] offsets = (np.random.random(beh_events.size) * exclude_shift / 2) * raw.info['sfreq'] beh_events += offsets beh_events -= beh_events[0] # throw off the alignment, make it harder beh_events_adjusted, best_events = _check_alignment( beh_events, candidates[2], candidates, candidates_set, resync_i) errors = beh_events_adjusted - best_events + candidates[2] assert all([ np.isnan(e) if i + 2 in corrupted_indices else e < exclude_shift_i for i, e in enumerate(errors) ]) beh_events_adjusted, alignment, best_events = _find_best_alignment( beh_events, candidates, exclude_shift, resync, raw.info['sfreq']) assert abs(alignment - candidates[2]) < exclude_shift_i errors = beh_events_adjusted - best_events + alignment assert all([ np.isnan(e) or abs(e) > exclude_shift_i if i + 2 in corrupted_indices else abs(e) < exclude_shift_i for i, e in enumerate(errors) ]) # test exclude ambiguous pd_events = _exclude_ambiguous_events(beh_events, alignment, best_events, pd, candidates, exclude_shift, max_len, raw.info['sfreq'], recover, zscore) assert all([i - 2 not in pd_events for i in corrupted_indices]) np.testing.assert_array_equal(pd_events[~np.isnan(pd_events)], events[2:, 0])