Skip to content

Conversation

@qian-chu
Copy link
Contributor

@qian-chu qian-chu commented Dec 30, 2025

Reference issue (if any)

Fixes #13567 and extend testing to the case mentioned in #13550.

What does this implement/fix?

As described in #13567, when a recording block starts with missing data, the status column sometimes is not present until actual data is present:

889502	   .	   .	    0.0
889503	   .	   .	    0.0
889504	  916.5	  575.5	  655.0	...
889505	  916.3	  575.2	  655.0	...

While it does not prevent the block to be read into a DataFrame (with four columns, in this case), the status column would start with None instead of any subscriptable string, causing a TypeError.

The fix instead gets the first valid value to infer if the column is status or not. Further, it updates the method to infer if the first value is numeric by trying to convert it to float.

Question: If not len(first_value) in [3, 5, 13, 17], should the code emit a warning/error to contact MNE developers?

Moreover, #13550 mentioned a scenerio that a recording block could be completely empty. While it doesn't seem to trigger any error anymore, this is not explicitly tested in the current tests. So we will also add this to the test coverage.

Minor fixes
  1. Correct typo ("occular" -> "ocular")
  2. if "PUPIL" in units, warn "Raw pupil position data detected" instead of "Raw eyegaze coordinates detected".

Additional information

The main focus of the PR is expanding _simulate_eye_tracking_data (which is already simulating multi-block data) to simulate more edge cases.

@qian-chu
Copy link
Contributor Author

Just noticed that #13550 and #13555 are also related. It seems the two problems are complementary - users will want to discard any empty recording block AND read blocks with an empty start

@qian-chu qian-chu changed the title Fix read_raw_eyelink() failure when status column starts with empty values Fix read_raw_eyelink() failure when recording blocks are empty or starting with empty values Jan 2, 2026
@qian-chu qian-chu marked this pull request as draft January 2, 2026 13:24
@scott-huberty
Copy link
Contributor

thanks @qian-chu ! And thanks in advance for your patience, just getting back to the office after the holiday break. I will take a closer look this week.

@qian-chu
Copy link
Contributor Author

qian-chu commented Jan 5, 2026

thanks @qian-chu ! And thanks in advance for your patience, just getting back to the office after the holiday break. I will take a closer look this week.

No worries! Not expecting people to be back from holidays this soon. This PR is also still in progress anyways (I have yet to add new tests)

@Cathaway
Copy link

Cathaway commented Jan 19, 2026

Minor fixes
  1. Correct typo ("occular" -> "ocular")
  2. if "PUPIL" in units, warn "Raw pupil position data detected" instead of "Raw eyegaze coordinates detected".

just a note 'pupil' channels in Eyelink measure pupil size (or diameter), not positions, so the warning message should be 'pupil size data detected', if a warning is needed

@qian-chu
Copy link
Contributor Author

Minor fixes
  1. Correct typo ("occular" -> "ocular")
  2. if "PUPIL" in units, warn "Raw pupil position data detected" instead of "Raw eyegaze coordinates detected".

just a note 'pupil' channels in Eyelink measure pupil size (or diameter), not positions, so the warning message should be 'pupil size data detected', if a warning is needed

This PUPIL unit, if I'm not mistaken, indicates the user used edf2asc -sp. According to edf2asc: "-sp outputs sample raw pupil position if present". This is not about pupil size, but what measurement the x and y are (default is gaze, could also be href and pupil).

@scott-huberty
Copy link
Contributor

Minor fixes
  1. Correct typo ("occular" -> "ocular")
  2. if "PUPIL" in units, warn "Raw pupil position data detected" instead of "Raw eyegaze coordinates detected".

just a note 'pupil' channels in Eyelink measure pupil size (or diameter), not positions, so the warning message should be 'pupil size data detected', if a warning is needed

This PUPIL unit, if I'm not mistaken, indicates the user used edf2asc -sp. According to edf2asc: "-sp outputs sample raw pupil position if present". This is not about pupil size, but what measurement the x and y are (default is gaze, could also be href and pupil).

Agreed. It is easy to mix up, but, In EyeLink systems there is a distinction between Pupil Size data and eyegaze pupil position data.

Per the EDF2ASC Manual:

SAMPLES < data type > < eyes > < data options >
This specifies what types of data is present in sample lines, as a sequence of keywords. The < data type > is one of "GAZE", "HREF" or "PUPIL".

And per section 4.4.2.1 PUPIL of the Eyelink 1000 Plus User manual (v1.0.19):

Pupil position data is raw (x, y) coordinate pairs from the camera. It has not been converted to eye angles or to gaze position.

@qian-chu I'm only loosely following this so feel free to ping me when you are ready for review or need my input!

@qian-chu
Copy link
Contributor Author

@scott-huberty Absolutely! I think it will be helpful if you could look at the evolution of conversation in #13550. It seems to have been solved by one of your previous PR but would love to hear your opinion. This will help determine the scope of this PR.

@qian-chu qian-chu marked this pull request as ready for review January 28, 2026 16:35
Comment on lines -385 to +391
fp.write("END\t7453390\tRIGHT\tSAMPLES\tEVENTS\n")
fp.write("END\t7453490\tRIGHT\tSAMPLES\tEVENTS\n")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This seems to be a small typo, because the timestamps are from 7453390 to 7453490, therefore block end message should be 7453490

Copy link
Contributor

Choose a reason for hiding this comment

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

thanks!

Comment on lines -429 to +446
assert raw.annotations.description[-7] == "BAD_ACQ_SKIP"
assert np.isclose(raw.annotations.onset[-7], 1.001)
assert np.isclose(raw.annotations.duration[-7], 0.1)
assert raw.annotations.description[-8] == "BAD_ACQ_SKIP"
assert np.isclose(raw.annotations.onset[-8], 1.001)
assert np.isclose(raw.annotations.duration[-8], 0.1)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Event indices all move "up" by one becase a new "BAD_ACQ_SKIP" is introduced between block 2 and 3.

@qian-chu
Copy link
Contributor Author

qian-chu commented Jan 28, 2026

This PR is ready for review! While I believe this should resolve the bug already, it also opens a new question: what should MNE do with empty recordings? The behavior currently seems to be still read the empty data in, but maybe it makes more sense to also denote the period as "BAD_ACQ_SKIP"? (which is currently only done to between-block gaps.) Open to ideas and implementations.

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.

read_raw_eyelink() fails to find and drop status column

4 participants