This page was generated from a Jupyter notebook. View on GitHub Binder badge

Events#

Events are time points marked during a recording, usually triggers that the experiment software sends to the eye tracker (e.g., “stimulus on”, “response”). Each event has an onset time and a label. This tutorial shows how to look at events, select the ones you need and change them, using the Events class.

[1]:
import pypillometry as pp
from pypillometry import u
import numpy as np
import matplotlib.pyplot as plt

pp.logging_set_level("WARNING")

d = pp.get_example_data("rlmw_002_short")

Getting the events#

d.events holds the events of a dataset as an Events object. In a notebook, it is shown as a table:

[2]:
events = d.events
events
[2]:
Onset (ms) Label
0 5177.0 F_1
1 5677.0 S_1_1_5
2 6773.0 C_1_5_2
3 7010.0 R_1_15
4 8776.0 F_2
5 9575.0 S_2_6_2
6 10581.0 C_2_6_1
7 10808.0 R_2_20
8 12374.0 F_3
9 12574.0 S_3_2_1
10 13733.0 C_3_2_1
11 13957.0 R_3_18
12 15973.0 F_4
13 16473.0 S_4_4_1
14 17589.0 C_4_1_2
15 17822.0 R_4_11
16 19588.0 F_5
17 20388.0 S_5_3_5
18 21156.0 C_5_5_2
19 21388.0 R_5_17
20 23187.0 F_6
21 23987.0 S_6_4_5
22 24916.0 C_6_5_2
23 25153.0 R_6_20
24 26802.0 F_7
25 27002.0 S_7_3_1
26 28116.0 C_7_1_2
27 28352.0 R_7_6
28 30401.0 F_8
29 30601.0 S_8_6_5
30 31508.0 C_8_6_1
31 31734.0 R_8_18
32 34000.0 F_9
33 34799.0 S_9_4_6
34 35700.0 C_9_6_2
35 35932.0 R_9_19
36 37615.0 F_10
37 38415.0 S_10_2_5
38 39412.0 C_10_5_2
39 39648.0 R_10_18

Total: 40 events

The onsets are in ms. to_units() returns the events with the onsets in other units:

[3]:
d.events.to_units("sec")[:4]
[3]:
Onset (sec) Label
0 5.177 F_1
1 5.677 S_1_1_5
2 6.773 C_1_5_2
3 7.010 R_1_15

Total: 4 events

[4]:
events.to_units("min").onsets[:4]
[4]:
array([0.08628333, 0.09461667, 0.11288333, 0.11683333])

The onsets and labels are stored as numpy arrays. An Events object has a length and can be iterated over and indexed like a list. Indexing returns a new Events object:

[5]:
print(len(events))
for onset, label in events[:3]:
    print(f"{label:10s} at {onset:.0f} ms")
40
F_1        at 5177 ms
S_1_1_5    at 5677 ms
C_1_5_2    at 6773 ms
[6]:
events.onsets[:5], events.labels[:5]
[6]:
(array([5177., 5677., 6773., 7010., 8776.]),
 array(['F_1', 'S_1_1_5', 'C_1_5_2', 'R_1_15', 'F_2'], dtype='<U8'))

Selecting events#

filter() returns the events that match a criterion. There are three kinds of criteria.

A string selects all labels that contain it:

[7]:
events.filter("S_")
[7]:
Onset (ms) Label
0 5677.0 S_1_1_5
1 9575.0 S_2_6_2
2 12574.0 S_3_2_1
3 16473.0 S_4_4_1
4 20388.0 S_5_3_5
5 23987.0 S_6_4_5
6 27002.0 S_7_3_1
7 30601.0 S_8_6_5
8 34799.0 S_9_4_6
9 38415.0 S_10_2_5

Total: 10 events

A function is called with each label and selects the events for which it returns True. In this dataset, the labels of the R events end with a number; here we select those where it is larger than 18:

[8]:
def high_number(label):
    parts = label.split("_")
    return parts[0] == "R" and int(parts[-1]) > 18

events.filter(high_number)
[8]:
Onset (ms) Label
0 10808.0 R_2_20
1 25153.0 R_6_20
2 35932.0 R_9_19

Total: 3 events

A tuple (start, end) selects events by their onset, in the units of the Events object (here, ms):

[9]:
events.filter((10_000, 20_000))
[9]:
Onset (ms) Label
0 10581.0 C_2_6_1
1 10808.0 R_2_20
2 12374.0 F_3
3 12574.0 S_3_2_1
4 13733.0 C_3_2_1
5 13957.0 R_3_18
6 15973.0 F_4
7 16473.0 S_4_4_1
8 17589.0 C_4_1_2
9 17822.0 R_4_11
10 19588.0 F_5

Total: 11 events

Since filter() returns a new Events object, filters can be chained, e.g., the S events in the first 20 seconds:

[10]:
events.filter("S_").filter((0, 20_000))
[10]:
Onset (ms) Label
0 5677.0 S_1_1_5
1 9575.0 S_2_6_2
2 12574.0 S_3_2_1
3 16473.0 S_4_4_1

Total: 4 events

test_selector() shows which events of a dataset a selector picks out, which is useful while writing a selection function:

[11]:
from pypillometry.convenience import test_selector
test_selector(d, high_number)
[11]:
array(['R_2_20', 'R_6_20', 'R_9_19'], dtype='<U8')

Plotting events#

plot() shows when the events happened. Each event is a point; the labels of some of them are shown (use show_labels="all" for all):

[12]:
plt.figure(figsize=(12, 4))
events.filter("S_").plot(units="sec", show_labels="all");
../_images/notebooks_events_21_0.png

Events are also shown in most plots of the data, e.g., in d.plot.plot_pupil().

Using events for intervals#

Analyses usually look at the data around events. get_epochs() takes the same kinds of selectors as filter() (string or function), and also an Events object. This makes it easy to use any selection of events, e.g., only the first five stimuli:

[13]:
first_stimuli = events.filter("S_")[:5]
d.get_epochs(first_stimuli, (-200, 800) * u.ms)
[13]:
Epochs 'events_5' | 5 epochs, window (-200, 800) ms

See the Intervals tutorial for more on intervals.

Creating and changing events#

Events objects can also be created directly. Onsets with units are converted, and the object keeps the units:

[14]:
my_events = pp.Events([1, 2.5, 4] * u.sec, ["start", "probe", "end"])
my_events
[14]:
Onset (sec) Label
0 1.0 start
1 2.5 probe
2 4.0 end

Total: 3 events

Events cannot be changed in place; set_events() replaces the events of a dataset with new ones. For example, we can shorten the labels to just the event type (the first letter). The onsets stay the same:

[15]:
short_labels = [label.split("_")[0] for label in events.labels]
d.set_events(pp.Events(events.onsets, short_labels))
d.events[:6]
[15]:
Onset (ms) Label
0 5177.0 F
1 5677.0 S
2 6773.0 C
3 7010.0 R
4 8776.0 F
5 9575.0 S

Total: 6 events

Like the other set_* methods, set_events() always changes the object in place; use d.copy().set_events(...) to keep the original.

As another example, we add the onset of each blink as an event. Blinks are stored as Intervals (see the blinks tutorial); we take their start times in ms, combine them with the existing events and sort everything by time:

[16]:
d.pupil.blinks_detect()
blinks = d.get_blinks("left", "pupil", units="ms")["left_pupil"]
blink_onsets = [start for start, end in blinks]

events = d.events
onsets = np.concatenate([events.onsets, blink_onsets])
labels = np.concatenate([events.labels, ["blink"] * len(blink_onsets)])
order = np.argsort(onsets)
d.set_events(pp.Events(onsets[order], labels[order]))

d.events[:12]
[16]:
Onset (ms) Label
0 5177.0 F
1 5408.0 blink
2 5677.0 S
3 6773.0 C
4 7010.0 R
5 7562.0 blink
6 7864.0 blink
7 8130.0 blink
8 8776.0 F
9 9575.0 S
10 10484.0 blink
11 10581.0 C

Total: 12 events

The blink onsets can now be used like any other event, e.g., to look at the pupil around blinks with d.get_epochs("blink", ...).

Metadata#

Events can also carry more information, such as the trial, the condition or the reaction time, decomposed from the labels or taken from a behaviour log. Events can then be selected by it, e.g., events.filter("S_", cond="easy"). See the event metadata tutorial.