Find Photodiode Events

In this example, we use pd-parser to find photodiode events and align them to behavior. Then, we save the data to BIDS format.

# Authors: Alex Rockhill <aprockhill@mailbox.org>
#
# License: BSD (3-clause)

Simulate data and use it to make a raw object

We’ll make an mne.io.Raw object so that we can save out some random data with a photodiode event channel in it in fif format (a commonly used electrophysiology data format).

import os.path as op
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)
raw.add_channels([raw2])
raw.info['line_freq'] = 60  # for bids

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
raw = _read_raw(fname)
After running this example, you can find the data here: /tmp/tmp_mne_tempdir_2_ee581j
Creating RawArray with float64 data, n_channels=1, n_times=2042106
    Range : 0 ... 2042105 =      0.000 ...  2042.105 secs
Ready.
Creating RawArray with float64 data, n_channels=3, n_times=2042106
    Range : 0 ... 2042105 =      0.000 ...  2042.105 secs
Ready.
Writing /tmp/tmp_mne_tempdir_2_ee581j/sub-1_task-mytask_raw.fif
Closing /tmp/tmp_mne_tempdir_2_ee581j/sub-1_task-mytask_raw.fif
[done]
Reading in /tmp/tmp_mne_tempdir_2_ee581j/sub-1_task-mytask_raw.fif
Opening raw data file /tmp/tmp_mne_tempdir_2_ee581j/sub-1_task-mytask_raw.fif...
    Range : 0 ... 2042105 =      0.000 ...  2042.105 secs
Ready.

Make behavior data

We’ll make a dictionary with lists for the events that are time-stamped when the photodiode was turned on and other events relative to those events. We’ll add some noise to the time-stamps so that we can see how behavior might look in an experimental setting. Let’s make a task where there is a fixation stimulus, then a go cue, and a then response as an example.

np.random.seed(12)
# add some noise to make it harder to align, use just over
# the exclusion of 0.03 to make some events excluded
offsets = np.random.random(n_events) * 0.035 - 0.0125
# in this example, the fixation would always be 700 ms
# after which point a cue would appear which is the "go time"
go_time = np.repeat(0.7, n_events)
# let's make the response time between 0.5 and 1.5 seconds uniform random
response_time = list(go_time + np.random.random(n_events) + 1.5)
for i in [10, 129, 232, 288]:
    response_time[i] = 'n/a'  # make some no responses


# put in dictionary to be converted to tsv file
beh['fix_onset_time'] = beh['time'] + offsets
beh['go_time'] = go_time
beh['response_time'] = response_time
behf = op.join(out_dir, 'sub-1_task-mytask_beh.tsv')
# save behavior file out
_to_tsv(behf, beh)

Use the interactive graphical user interface (GUI) to find parameters

On the documentation webpage, this is example is not interactive, but if you download it as a jupyter notebook and run it or copy the code into a console running python (ipython recommended), you can see how to interact with the photodiode data to pick reasonable parameters by following the instructions.

pd_parser.find_pd_params(raw, pd_ch_names=['pd'])
Align Use the left/right keys to find an uncorrupted photodiode event and align the onset to the center of the window use +/- to zoom the yaxis in and out (up/down to translate) press enter when finished
Reading 0 ... 2042105  =      0.000 ...  2042.105 secs...

Find the photodiode events relative to the behavioral timing of interest

This function will use the default parameters or the parameters you found from pd_parser.find_pd_parameters() to find and align the photodiode events, excluding events that were off because the computer hung up on computation, for instance. That data is saved in the same folder as the raw file (in this case, a temperary directory generated by _TempDir()). The data can be used directly, or it can be accessed via pd_parser.pd_parser_save_to_bids() to store it in the brain imagine data structure (BIDS) standardized format before using it.

pd_parser.parse_pd(raw, beh=beh, pd_ch_names=['pd'], max_len=1.5)
  • Alignment (First 10), Event Differences
  • Excluded Events, 1 off by 34 ms, 57 none found, 76 off by -30 ms, 96 none found, 97 off by 32 ms, 98 off by -31 ms, 172 none found
Reading 0 ... 2042105  =      0.000 ...  2042.105 secs...
Finding photodiode events

  0%|          | 0/793 [00:00<?, ?it/s]
 77%|#######6  | 607/793 [00:00<00:00, 6065.00it/s]
100%|##########| 793/793 [00:00<00:00, 6431.41it/s]
299 up-deflection photodiode candidate events found
Checking best alignments

  0%|          | 0/300 [00:00<?, ?it/s]
  1%|1         | 3/300 [00:00<00:10, 27.80it/s]
  2%|2         | 6/300 [00:00<00:10, 27.93it/s]
  3%|3         | 9/300 [00:00<00:10, 27.74it/s]
  4%|4         | 12/300 [00:00<00:10, 27.69it/s]
  5%|5         | 15/300 [00:00<00:10, 27.66it/s]
  6%|6         | 18/300 [00:00<00:10, 27.67it/s]
  7%|7         | 21/300 [00:00<00:10, 27.41it/s]
  8%|8         | 24/300 [00:00<00:10, 27.42it/s]
  9%|9         | 27/300 [00:00<00:10, 27.28it/s]
 10%|#         | 30/300 [00:01<00:09, 27.38it/s]
 11%|#1        | 33/300 [00:01<00:09, 27.20it/s]
 12%|#2        | 36/300 [00:01<00:09, 27.23it/s]
 13%|#3        | 39/300 [00:01<00:09, 27.16it/s]
 14%|#4        | 42/300 [00:01<00:09, 27.01it/s]
 15%|#5        | 45/300 [00:01<00:09, 26.43it/s]
 16%|#6        | 48/300 [00:01<00:09, 26.71it/s]
 17%|#7        | 51/300 [00:01<00:09, 26.93it/s]
 18%|#8        | 54/300 [00:01<00:09, 26.79it/s]
 19%|#9        | 57/300 [00:02<00:09, 26.80it/s]
 20%|##        | 60/300 [00:02<00:09, 25.69it/s]
 21%|##1       | 63/300 [00:02<00:09, 25.77it/s]
 22%|##2       | 66/300 [00:02<00:09, 25.69it/s]
 23%|##3       | 69/300 [00:02<00:08, 25.81it/s]
 24%|##4       | 72/300 [00:02<00:08, 26.30it/s]
 25%|##5       | 75/300 [00:02<00:08, 26.70it/s]
 26%|##6       | 78/300 [00:02<00:08, 26.81it/s]
 27%|##7       | 81/300 [00:03<00:08, 27.01it/s]
 28%|##8       | 84/300 [00:03<00:08, 26.76it/s]
 29%|##9       | 87/300 [00:03<00:08, 25.96it/s]
 30%|###       | 90/300 [00:03<00:08, 26.18it/s]
 31%|###1      | 93/300 [00:03<00:07, 26.02it/s]
 32%|###2      | 96/300 [00:03<00:08, 25.37it/s]
 33%|###3      | 99/300 [00:03<00:08, 24.70it/s]
 34%|###4      | 102/300 [00:03<00:07, 25.14it/s]
 35%|###5      | 105/300 [00:03<00:07, 25.58it/s]
 36%|###6      | 108/300 [00:04<00:07, 25.72it/s]
 37%|###7      | 111/300 [00:04<00:07, 25.98it/s]
 38%|###8      | 114/300 [00:04<00:07, 26.13it/s]
 39%|###9      | 117/300 [00:04<00:06, 26.20it/s]
 40%|####      | 120/300 [00:04<00:06, 26.01it/s]
 41%|####1     | 123/300 [00:04<00:06, 25.93it/s]
 42%|####2     | 126/300 [00:04<00:06, 26.03it/s]
 43%|####3     | 129/300 [00:04<00:06, 26.13it/s]
 44%|####4     | 132/300 [00:04<00:06, 26.37it/s]
 45%|####5     | 135/300 [00:05<00:06, 26.37it/s]
 46%|####6     | 138/300 [00:05<00:06, 26.14it/s]
 47%|####6     | 141/300 [00:05<00:06, 26.24it/s]
 48%|####8     | 144/300 [00:05<00:06, 25.94it/s]
 49%|####9     | 147/300 [00:05<00:05, 25.64it/s]
 50%|#####     | 150/300 [00:05<00:05, 25.70it/s]
 51%|#####1    | 153/300 [00:05<00:05, 25.72it/s]
 52%|#####2    | 156/300 [00:05<00:05, 25.68it/s]
 53%|#####3    | 159/300 [00:06<00:05, 24.69it/s]
 54%|#####4    | 162/300 [00:06<00:05, 24.91it/s]
 55%|#####5    | 165/300 [00:06<00:05, 25.08it/s]
 56%|#####6    | 168/300 [00:06<00:05, 25.05it/s]
 57%|#####6    | 171/300 [00:06<00:05, 25.22it/s]
 58%|#####8    | 174/300 [00:06<00:05, 24.44it/s]
 59%|#####8    | 177/300 [00:06<00:05, 24.33it/s]
 60%|######    | 180/300 [00:06<00:04, 24.80it/s]
 61%|######1   | 183/300 [00:07<00:04, 25.21it/s]
 62%|######2   | 186/300 [00:07<00:04, 25.39it/s]
 63%|######3   | 189/300 [00:07<00:04, 25.35it/s]
 64%|######4   | 192/300 [00:07<00:04, 25.24it/s]
 65%|######5   | 195/300 [00:07<00:04, 25.39it/s]
 66%|######6   | 198/300 [00:07<00:04, 25.37it/s]
 67%|######7   | 201/300 [00:07<00:04, 24.45it/s]
 68%|######8   | 204/300 [00:07<00:03, 24.67it/s]
 69%|######9   | 207/300 [00:07<00:03, 24.88it/s]
 70%|#######   | 210/300 [00:08<00:03, 24.96it/s]
 71%|#######1  | 213/300 [00:08<00:03, 25.07it/s]
 72%|#######2  | 216/300 [00:08<00:03, 25.23it/s]
 73%|#######3  | 219/300 [00:08<00:03, 25.39it/s]
 74%|#######4  | 222/300 [00:08<00:03, 25.04it/s]
 75%|#######5  | 225/300 [00:08<00:02, 25.12it/s]
 76%|#######6  | 228/300 [00:08<00:02, 25.13it/s]
 77%|#######7  | 231/300 [00:08<00:02, 25.07it/s]
 78%|#######8  | 234/300 [00:09<00:02, 24.94it/s]
 79%|#######9  | 237/300 [00:09<00:02, 25.12it/s]
 80%|########  | 240/300 [00:09<00:02, 24.95it/s]
 81%|########1 | 243/300 [00:09<00:02, 24.89it/s]
 82%|########2 | 246/300 [00:09<00:02, 24.85it/s]
 83%|########2 | 249/300 [00:09<00:02, 24.80it/s]
 84%|########4 | 252/300 [00:09<00:02, 23.54it/s]
 85%|########5 | 255/300 [00:09<00:01, 23.40it/s]
 86%|########6 | 258/300 [00:10<00:01, 23.32it/s]
 87%|########7 | 261/300 [00:10<00:01, 21.99it/s]
 88%|########8 | 264/300 [00:10<00:01, 20.20it/s]
 89%|########9 | 267/300 [00:10<00:01, 19.39it/s]
 90%|######### | 270/300 [00:10<00:01, 19.36it/s]
 91%|#########1| 273/300 [00:10<00:01, 19.89it/s]
 92%|#########2| 276/300 [00:10<00:01, 20.38it/s]
 93%|#########3| 279/300 [00:11<00:01, 20.84it/s]
 94%|#########3| 282/300 [00:11<00:00, 21.23it/s]
 95%|#########5| 285/300 [00:11<00:00, 20.93it/s]
 96%|#########6| 288/300 [00:11<00:00, 21.11it/s]
 97%|#########7| 291/300 [00:11<00:00, 21.48it/s]
 98%|#########8| 294/300 [00:11<00:00, 20.78it/s]
 99%|#########9| 297/300 [00:11<00:00, 20.74it/s]
100%|##########| 300/300 [00:12<00:00, 21.22it/s]
100%|##########| 300/300 [00:12<00:00, 24.76it/s]
Best alignment is with the first behavioral event shifted 0.00 s relative to the first synchronization event and has errors: min -31.60 ms, q1 -9.87 ms, med 0.17 ms, q3 9.68 ms, max 34.10 ms, 3 missed events
Excluding events that have zero close synchronization events or more than one synchronization event within `max_len` time
1 off by 34 ms
57 none found
76 off by -30 ms
96 none found
97 off by 32 ms
98 off by -31 ms
172 none found

(<Annotations | 293 segments: Fixation (293)>, [12270, 'n/a', 24994, 32081, 38711, 45439, 51458, 58189, 65602, 72878, 79973, 86136, 93477, 100763, 107011, 113960, 119991, 126166, 132641, 138878, 146016, 153243, 159760, 166238, 172405, 178531, 185600, 192499, 198583, 205303, 211906, 219178, 226255, 233158, 239987, 247411, 254891, 261398, 267758, 274953, 281049, 287596, 293701, 300180, 306286, 312721, 319906, 327264, 334453, 341296, 348220, 354762, 361015, 367669, 374768, 380862, 386893, 'n/a', 400499, 407551, 414653, 422052, 428652, 435190, 442400, 449547, 456526, 463742, 470705, 478141, 484642, 491749, 499174, 505676, 512593, 519142, 'n/a', 531947, 537976, 545115, 551794, 558728, 565838, 572466, 579017, 585271, 592462, 599712, 606813, 614126, 621098, 628142, 634432, 641583, 648084, 654741, 'n/a', 'n/a', 'n/a', 681922, 688399, 694726, 701993, 708446, 715103, 721240, 727691, 733820, 740336, 747400, 754861, 761799, 768203, 775188, 782128, 789366, 796167, 803534, 810157, 816586, 823365, 830749, 837835, 844576, 851322, 858295, 864967, 871793, 879250, 886649, 893910, 900101, 906936, 913486, 920854, 927325, 934721, 941079, 947710, 955150, 962043, 969441, 976249, 982591, 989660, 996442, 1003200, 1009405, 1015622, 1023016, 1030450, 1037899, 1044436, 1050505, 1057336, 1064440, 1071412, 1077491, 1083840, 1090899, 1096982, 1104252, 1111736, 1118108, 1124189, 1131659, 1138810, 1145491, 1152208, 1159581, 1166115, 1172195, 'n/a', 1185585, 1192607, 1200029, 1206570, 1212940, 1219019, 1226266, 1232384, 1239812, 1246871, 1254238, 1261460, 1267746, 1274010, 1281452, 1287889, 1295075, 1301331, 1307780, 1314790, 1321163, 1328291, 1335610, 1342091, 1348539, 1354581, 1361215, 1367313, 1374175, 1381207, 1388059, 1394965, 1401864, 1409218, 1416015, 1423307, 1429835, 1436046, 1442379, 1449569, 1455914, 1463073, 1470427, 1477244, 1483459, 1490394, 1496963, 1504326, 1510661, 1517012, 1523736, 1530476, 1536758, 1543320, 1550322, 1557028, 1563118, 1569741, 1576365, 1583036, 1589072, 1595483, 1602909, 1610234, 1617645, 1624783, 1631434, 1638840, 1645291, 1652181, 1659062, 1665723, 1672099, 1678595, 1685559, 1691985, 1698504, 1705035, 1711522, 1718765, 1725584, 1731612, 1738724, 1745477, 1752645, 1759387, 1765853, 1773112, 1779372, 1785789, 1792515, 1799805, 1805978, 1812486, 1819284, 1826302, 1832460, 1839593, 1846428, 1853204, 1859775, 1866247, 1873583, 1881074, 1887337, 1893654, 1900655, 1907802, 1915036, 1921490, 1927804, 1933913, 1940477, 1947321, 1954630, 1960680, 1966814, 1973794, 1980801, 1986987, 1993132, 1999174, 2006147, 2012567, 2018928, 2025925, 2032107])

Add events relative to the photodiode events

The photodiode is usually sychronized to one event (e.g. the fixation so that if the deflections caused by the photodiode are large enough to influence other channels through amplifier interactions it doesn’t cause issues with the analysis) so often the events of interest are relative to the photodiode event. In the task a timer can be started at the photodiode event and checked each time a subsequent event occurs. These events should then be recorded in tsv file, which can be passed to pd-parser in order to add the events. Note: if more than one photodiode event is used, the parser can be used for each event separately using the keyword add_event=True.

pd_parser.add_relative_events(
    raw, beh,
    relative_event_keys=['go_time', 'response_time'],
    relative_event_names=['Go Cue', 'Response'])
Overwriting existing file.

<Annotations | 875 segments: Fixation (293), Go Cue (293), Response (289)>

Save data to BIDS format

This saves our data to BIDS format so that it’s ready to be analyzed in a reproducible way; BIDS requires all the files the BIDS community has deemed necessary for analysis, so you should have everything you need to continue on with an analysis at this point. See https://bids.neuroimaging.io/ and https://mne.tools/mne-bids/ for more information about BIDS.

pd_parser.save_to_bids(op.join(out_dir, 'bids_dir'), fname,
                       sub='1', task='mytask')
Reading in /tmp/tmp_mne_tempdir_2_ee581j/sub-1_task-mytask_raw.fif
Opening raw data file /tmp/tmp_mne_tempdir_2_ee581j/sub-1_task-mytask_raw.fif...
    Range : 0 ... 2042105 =      0.000 ...  2042.105 secs
Ready.
Used Annotations descriptions: ['Fixation', 'Go Cue', 'Response']
Opening raw data file /tmp/tmp_mne_tempdir_2_ee581j/sub-1_task-mytask_raw.fif...
    Range : 0 ... 2042105 =      0.000 ...  2042.105 secs
Ready.
Writing '/tmp/tmp_mne_tempdir_2_ee581j/bids_dir/README'...
Writing '/tmp/tmp_mne_tempdir_2_ee581j/bids_dir/participants.tsv'...
Writing '/tmp/tmp_mne_tempdir_2_ee581j/bids_dir/participants.json'...
Used Annotations descriptions: ['Fixation', 'Go Cue', 'Response']
Writing '/tmp/tmp_mne_tempdir_2_ee581j/bids_dir/sub-1/ieeg/sub-1_task-mytask_events.tsv'...
Writing '/tmp/tmp_mne_tempdir_2_ee581j/bids_dir/dataset_description.json'...
Writing '/tmp/tmp_mne_tempdir_2_ee581j/bids_dir/sub-1/ieeg/sub-1_task-mytask_ieeg.json'...
Writing '/tmp/tmp_mne_tempdir_2_ee581j/bids_dir/sub-1/ieeg/sub-1_task-mytask_channels.tsv'...
/home/alexrockhill/software/anaconda3/envs/mne/lib/python3.10/site-packages/mne_bids/write.py:1848: RuntimeWarning: Converting data files to BrainVision format
  warn('Converting data files to BrainVision format')
Writing '/tmp/tmp_mne_tempdir_2_ee581j/bids_dir/sub-1/sub-1_scans.tsv'...
Wrote /tmp/tmp_mne_tempdir_2_ee581j/bids_dir/sub-1/sub-1_scans.tsv entry with ieeg/sub-1_task-mytask_ieeg.vhdr.

Total running time of the script: ( 0 minutes 16.826 seconds)

Gallery generated by Sphinx-Gallery