Skip to content

Commit 2ce64e6

Browse files
scott-hubertylarsoner
authored andcommitted
FIX: Handle EyeLink Files with Eye Event Data set to "HREF" (mne-tools#13357)
Co-authored-by: Eric Larson <[email protected]>
1 parent 46f9847 commit 2ce64e6

File tree

3 files changed

+56
-11
lines changed

3 files changed

+56
-11
lines changed

doc/changes/dev/13357.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make :func:`~mne.io.read_raw_eyelink` work with ASCII files collected with Eye Event Mode set to "HREF" by `Scott Huberty`_.

mne/io/eyelink/_utils.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"sacc_visual_angle",
3939
"peak_velocity",
4040
),
41+
"messages": ("time", "offset", "event_msg"),
4142
}
4243

4344

@@ -373,6 +374,7 @@ def _infer_col_names(raw_extras):
373374
col_names = {}
374375
# initiate the column names for the sample lines
375376
col_names["samples"] = list(EYELINK_COLS["timestamp"])
377+
col_names["messages"] = list(EYELINK_COLS["messages"])
376378

377379
# and for the eye message lines
378380
col_names["blinks"] = list(EYELINK_COLS["eye_event"])
@@ -427,16 +429,33 @@ def _assign_col_names(col_names, df_dict):
427429
col_names : dict
428430
Dictionary of column names for each dataframe.
429431
"""
432+
skipped_types = []
430433
for key, df in df_dict.items():
431-
if key in ("samples", "blinks", "fixations", "saccades"):
432-
df.columns = col_names[key]
433-
elif key == "messages":
434-
cols = ["time", "offset", "event_msg"]
435-
df.columns = cols
436-
# added for buttons
437-
elif key == "buttons":
438-
cols = ["time", "button_id", "button_pressed"]
439-
df.columns = cols
434+
if key in ("samples", "blinks", "fixations", "saccades", "messages"):
435+
cols = col_names[key]
436+
else:
437+
skipped_types.append(key)
438+
continue
439+
max_cols = len(cols)
440+
if len(df.columns) != len(cols):
441+
if key in ("saccades", "fixations") and len(df.columns) >= 4:
442+
# see https://github.com/mne-tools/mne-python/pull/13357
443+
logger.debug(
444+
f"{key} events have more columns ({len(df.columns)}) than "
445+
f"expected ({len(cols)}). Using first 4 (eye, time, end_time, "
446+
"duration)."
447+
)
448+
max_cols = 4
449+
else:
450+
raise ValueError(
451+
f"Expected the {key} data in this file to have {len(cols)} columns "
452+
f"of data, but got {len(df.columns)}. Expected columns: {cols}."
453+
)
454+
new_col_names = {
455+
old: new for old, new in zip(df.columns[:max_cols], cols[:max_cols])
456+
}
457+
df.rename(columns=new_col_names, inplace=True)
458+
logger.debug(f"Skipped assigning column names to {skipped_types} dataframes.")
440459
return df_dict
441460

442461

@@ -495,10 +514,10 @@ def _convert_times(df, first_samp, col="time"):
495514
"""
496515
_sort_by_time(df, col)
497516
for col in df.columns:
498-
if col.endswith("time"): # 'time' and 'end_time' cols
517+
if str(col).endswith("time"): # 'time' and 'end_time' cols
499518
df[col] -= first_samp
500519
df[col] /= 1000
501-
if col in ["duration", "offset"]:
520+
if str(col) in ["duration", "offset"]:
502521
df[col] /= 1000
503522
return df
504523

mne/io/eyelink/tests/test_eyelink.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,28 @@ def test_no_datetime(tmp_path):
381381
# Sanity check that a None meas_date doesn't change annotation times
382382
# First annotation in this file is a fixation at 0.004 seconds
383383
np.testing.assert_allclose(raw.annotations.onset[0], 0.004)
384+
385+
386+
@requires_testing_data
387+
def test_href_eye_events(tmp_path):
388+
"""Test Parsing file where Eye Event Data option was set to 'HREF'."""
389+
out_file = tmp_path / "tmp_eyelink.asc"
390+
lines = fname_href.read_text("utf-8").splitlines()
391+
for li, line in enumerate(lines):
392+
if not line.startswith(("ESACC", "EFIX")):
393+
continue
394+
tokens = line.split()
395+
if line.startswith("ESACC"):
396+
href_sacc_vals = ["9999", "9999", "9999", "9999", "99.99", "999"]
397+
tokens[5:5] = href_sacc_vals # add href saccade values
398+
elif line.startswith("EFIX"):
399+
tokens = line.split()
400+
href_fix_vals = ["9999.9", "9999.9", "999"]
401+
tokens[5:3] = href_fix_vals
402+
new_line = "\t".join(tokens) + "\n"
403+
lines[li] = new_line
404+
out_file.write_text("\n".join(lines), encoding="utf-8")
405+
raw = read_raw_eyelink(out_file)
406+
# Just check that we actually parsed the Saccade and Fixation events
407+
assert "saccade" in raw.annotations.description
408+
assert "fixation" in raw.annotations.description

0 commit comments

Comments
 (0)