Skip to content

Commit

Permalink
ENH: Read and store "START" and "END" events from EDF file (#22)
Browse files Browse the repository at this point in the history
* ENH: Read and store "START" and "END" events from EDF file

These events indicate when the Eyetracking started and stopped recording, respectively. There can be multiple of these events, meaning that the eyetracker started and paused (e.g. during a calibration sequence or between trials).

The pyeparse authors appeared to know about "START" and "END events in EDF files, but they just used python's `pass` to skip handling these events.

I adjusted our event handlers to give both start and end events an entry in `EDF["discrete"].

It's important to store this info because for some recordings (read: users) there is a time gap between an end event and the subsequent start event. In other words, there is a pause/gap in the recording that the user might want to know about.

* TST: add a unit test for start and end events
  • Loading branch information
scott-huberty authored Feb 11, 2025
1 parent 1d57c07 commit aabcf18
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 3 deletions.
21 changes: 18 additions & 3 deletions src/eyelinkio/edf/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ def __exit__(self, type_, value, traceback):
BUTTONEVENT="buttons",
INPUTEVENT="inputs",
MESSAGEEVENT="messages",
STARTEVENTS="starts",
ENDEVENTS="ends",
)


Expand Down Expand Up @@ -209,7 +211,16 @@ def _read_raw_edf(fname):
#
discrete = res["discrete"]
info = res["info"]
event_types = ("saccades", "fixations", "blinks", "buttons", "inputs", "messages")
event_types = (
"saccades",
"fixations",
"blinks",
"buttons",
"inputs",
"messages",
"starts",
"ends",
)
info["sample_fields"] = info["sample_fields"][1:] # omit time

#
Expand Down Expand Up @@ -541,6 +552,10 @@ def _handle_end(edf, res, name):
f = ["sttime", "buttons"]
elif name == "inputs":
f = ["sttime", "input"]
elif name == "starts":
f = ["sttime"]
elif name == "ends":
f = ["sttime"]
else:
raise KeyError(f"Unknown name {name}")
res["edf_fields"][name] = f
Expand Down Expand Up @@ -592,7 +607,7 @@ def _handle_version(res):
BREAKPARSE=_handle_pass,
STARTSAMPLES=_handle_pass,
ENDSAMPLES=_handle_pass,
STARTEVENTS=_handle_pass,
ENDEVENTS=_handle_pass,
STARTEVENTS=partial(_handle_end, name="starts"),
ENDEVENTS=partial(_handle_end, name="ends"),
VERSION=_handle_version,
)
4 changes: 4 additions & 0 deletions src/eyelinkio/tests/test_edf.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ def test_read_raw():
# XXX: ideally we should get a binocular file with a calibration
assert edf_file["info"]["eye"] == "BINOCULAR"
assert len(edf_file["discrete"]["blinks"]) == 195
np.testing.assert_equal(edf_file["discrete"]["starts"]["stime"][0], 0.0)
np.testing.assert_almost_equal(
edf_file["discrete"]["ends"]["stime"][-1], 199.644
)

elif fname.name == "test_2_raw.edf": # First test file has this property
for kind in ['saccades', 'fixations', 'blinks']:
Expand Down

0 comments on commit aabcf18

Please sign in to comment.