Skip to content

Commit 5b575a6

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 c234aef commit 5b575a6

File tree

3 files changed

+56
-7
lines changed

3 files changed

+56
-7
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 & 7 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

@@ -356,6 +357,7 @@ def _infer_col_names(raw_extras):
356357
col_names = {}
357358
# initiate the column names for the sample lines
358359
col_names["samples"] = list(EYELINK_COLS["timestamp"])
360+
col_names["messages"] = list(EYELINK_COLS["messages"])
359361

360362
# and for the eye message lines
361363
col_names["blinks"] = list(EYELINK_COLS["eye_event"])
@@ -410,12 +412,33 @@ def _assign_col_names(col_names, df_dict):
410412
col_names : dict
411413
Dictionary of column names for each dataframe.
412414
"""
415+
skipped_types = []
413416
for key, df in df_dict.items():
414-
if key in ("samples", "blinks", "fixations", "saccades"):
415-
df.columns = col_names[key]
416-
elif key == "messages":
417-
cols = ["time", "offset", "event_msg"]
418-
df.columns = cols
417+
if key in ("samples", "blinks", "fixations", "saccades", "messages"):
418+
cols = col_names[key]
419+
else:
420+
skipped_types.append(key)
421+
continue
422+
max_cols = len(cols)
423+
if len(df.columns) != len(cols):
424+
if key in ("saccades", "fixations") and len(df.columns) >= 4:
425+
# see https://github.com/mne-tools/mne-python/pull/13357
426+
logger.debug(
427+
f"{key} events have more columns ({len(df.columns)}) than "
428+
f"expected ({len(cols)}). Using first 4 (eye, time, end_time, "
429+
"duration)."
430+
)
431+
max_cols = 4
432+
else:
433+
raise ValueError(
434+
f"Expected the {key} data in this file to have {len(cols)} columns "
435+
f"of data, but got {len(df.columns)}. Expected columns: {cols}."
436+
)
437+
new_col_names = {
438+
old: new for old, new in zip(df.columns[:max_cols], cols[:max_cols])
439+
}
440+
df.rename(columns=new_col_names, inplace=True)
441+
logger.debug(f"Skipped assigning column names to {skipped_types} dataframes.")
419442
return df_dict
420443

421444

@@ -474,10 +497,10 @@ def _convert_times(df, first_samp, col="time"):
474497
"""
475498
_sort_by_time(df, col)
476499
for col in df.columns:
477-
if col.endswith("time"): # 'time' and 'end_time' cols
500+
if str(col).endswith("time"): # 'time' and 'end_time' cols
478501
df[col] -= first_samp
479502
df[col] /= 1000
480-
if col in ["duration", "offset"]:
503+
if str(col) in ["duration", "offset"]:
481504
df[col] /= 1000
482505
return df
483506

mne/io/eyelink/tests/test_eyelink.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,28 @@ def test_no_datetime(tmp_path):
339339
# Sanity check that a None meas_date doesn't change annotation times
340340
# First annotation in this file is a fixation at 0.004 seconds
341341
np.testing.assert_allclose(raw.annotations.onset[0], 0.004)
342+
343+
344+
@requires_testing_data
345+
def test_href_eye_events(tmp_path):
346+
"""Test Parsing file where Eye Event Data option was set to 'HREF'."""
347+
out_file = tmp_path / "tmp_eyelink.asc"
348+
lines = fname_href.read_text("utf-8").splitlines()
349+
for li, line in enumerate(lines):
350+
if not line.startswith(("ESACC", "EFIX")):
351+
continue
352+
tokens = line.split()
353+
if line.startswith("ESACC"):
354+
href_sacc_vals = ["9999", "9999", "9999", "9999", "99.99", "999"]
355+
tokens[5:5] = href_sacc_vals # add href saccade values
356+
elif line.startswith("EFIX"):
357+
tokens = line.split()
358+
href_fix_vals = ["9999.9", "9999.9", "999"]
359+
tokens[5:3] = href_fix_vals
360+
new_line = "\t".join(tokens) + "\n"
361+
lines[li] = new_line
362+
out_file.write_text("\n".join(lines), encoding="utf-8")
363+
raw = read_raw_eyelink(out_file)
364+
# Just check that we actually parsed the Saccade and Fixation events
365+
assert "saccade" in raw.annotations.description
366+
assert "fixation" in raw.annotations.description

0 commit comments

Comments
 (0)