Skip to content

Conversation

WouterKroot
Copy link

@WouterKroot WouterKroot commented Aug 14, 2025

Reference issue (if any)

What does this implement/fix?

To enable the parsing of button presses, I changed the eyelink utilities and tests. Mainly, added button events to the eyelink data simulation, some tests to see if they are correctly parsed, and make sure that associated timing is parsed.

Additional information

My messy git prevented me to rebase, so was forced to create a new request.

Copy link
Contributor

@scott-huberty scott-huberty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @WouterKroot ! Per your messages in other threads (ref #13287 #13253 ), I really appreciate the time you've put into this and that you have other competing priorities that limit the amount of time you can contribute to MNE. That being said, I think this is really close. My comments mostly pertain to house keeping.

Since you've initiated this new PR, we don't have to worry about the rebase issues in #13287. Lastly, can you edit your message at the top of this thread, and add "fixes #13250"?

EDIT: Oh and one more thing. @WouterKroot please add a change log to mne-python/doc/changes/dev/13379.newfeature.rst. See the other changelog entries in the dev sub directory to get an idea of what to write. And go ahead and add your name to mne-python/doc/changes/dev/changes.inc if you are not already in there. Refer to the MNE-Python contributing guide for more details

descriptions = "BAD_" + descriptions

ch_names = df["eye"].map(eye_ch_map).tolist()
# breakpoint() # debug
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# breakpoint() # debug

Comment on lines +918 to +923

def get_button_description(row):
button_id = int(row["button_id"])
action = "press" if row["button_pressed"] == 1 else "release"
return f"button_{button_id}_{action}"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MNE-Python usually prefers to use private functions (placed at the module level) over inner functions (ref style guide). Can you prepend this function name with an underscore and move it out of the _make_eyelink_annots function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume that this was something you used during development, but we don't need it in our codebase. Can you remove this file from this PR?

Comment on lines +525 to +526
# print(f"\nLine {li}: {line}")
# print(f"Tokens ({len(tokens)}): {tokens}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# print(f"\nLine {li}: {line}")
# print(f"Tokens ({len(tokens)}): {tokens}")

assert np.isclose(raw.annotations.onset[-1], 1.001)
assert np.isclose(raw.annotations.duration[-1], 0.1)

print("\n=== Annotations ===")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print("\n=== Annotations ===")

Comment on lines +433 to +435
print(f"{onset:.3f}s dur={duration:.3f}s {desc}")

print("\n=== Recording block markers ===")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print(f"{onset:.3f}s dur={duration:.3f}s {desc}")
print("\n=== Recording block markers ===")

Comment on lines +437 to +439
if desc == "BAD_ACQ_SKIP":
print(f"BAD_ACQ_SKIP at {onset:.3f}s")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if desc == "BAD_ACQ_SKIP":
print(f"BAD_ACQ_SKIP at {onset:.3f}s")

Just FYI, MNE has its own logger function for sending output to the console, but more to the point, in MNE we don't normally print out information from tests.

elif (key == "buttons") and (key in descs):
required_cols = {"time", "button_id", "button_pressed"}
if not required_cols.issubset(df.columns):
raise ValueError(f"Missing column: {required_cols - set(df.columns)}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if condition is not covered by a test. I'm wondering, is there some plausible scenario where we would expect these column names ('time', 'button_id', 'button_pressed') to be missing from the buttons dataframe? It looks like you hard code these column names during button dataframe creation, so I would expect that they will always be present? If so, I think this check is unnecessary.

Comment on lines +418 to +419
else:
logger.info("No button events found in this file.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
else:
logger.info("No button events found in this file.")

So this else clause is happening at the acquisition block level. I noticed with my files that have multiple acquisition blocks, "No button events found in this file" gets printed to my console multiple times. But it should only be printed once.

I think that we have a few options.

  1. use the DEBUG level (logger.debug)
  2. Omit this logger message entirely.
  3. move this check to _validate_data or something like that so it only gets printed to the console once per file.

@larsoner
Copy link
Member

@WouterKroot are you planning to work on this one again? Or would it help if we (probably @scott-huberty) pushed some commits?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants