Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH, FIX: Make eyelink occular annotations "channel aware", and call blink annotations "BAD" #11746

Merged
merged 10 commits into from Jun 23, 2023

Conversation

scott-huberty
Copy link
Contributor

@scott-huberty scott-huberty commented Jun 21, 2023

Reference issue

This PR is in reference to the discussion in PR #11740 , see this comment

  • Note to self: Need to add this PR to the change log

What does this implement/fix?

  1. occular annotations ('fixations', 'saccades', 'blinks') are now channel aware.
  2. blink annotation descriptions are now prepended with BAD_
  3. Tests: I added tests to account for point 1 and 2 above.
  4. Clean up: There were a lot of helper functions sitting in mne.io.eyelink, I moved them to the _utils.py file that sits in the same directory.

Example:

import mne
fname = mne.datasets.testing.data_path() / "eyetrack" / "test_eyelink.asc"
raw = mne.io.read_raw_eyelink(fname,
                              create_annotations=["blinks"],
                              find_overlaps=True)
raw.plot(scalings='auto', start=20)
print(raw.annotations[5])

bad_blink

OrderedDict([('onset', 28.551),
             ('duration', 0.08),
             ('description', 'BAD_blink_both'),
             ('orig_time',
              datetime.datetime(2022, 3, 10, 11, 38, 16, tzinfo=datetime.timezone.utc)),
             ('ch_names',
              ('xpos_left',
               'ypos_left',
               'pupil_left',
               'xpos_right',
               'ypos_right',
               'pupil_right'))])

To clena up the module with read_raw_eyelink
- This required slight changes to the eyelink tests to account for the new description
@scott-huberty scott-huberty changed the title Read raw eyelink annots ENH, FIX: Make eyelink occular annotations "channel aware", and call blink annotations "BAD" Jun 21, 2023
- added an example of the ch_names annotation key. and mentioned that blinks are considered bad
@scott-huberty
Copy link
Contributor Author

scott-huberty commented Jun 21, 2023

Note: Updated change log and updated the eye-tracking tutorials to mention/display the ch_names annotation key.

EDIT: here: https://output.circle-artifacts.com/output/job/e3c7ced6-00ef-49d6-9acd-bd95a5073da9/artifacts/0/html/auto_tutorials/preprocessing/90_eyetracking_data.html

doc/changes/latest.inc Outdated Show resolved Hide resolved
token : str
Single element from tokens list.
"""
if isinstance(token, str):
Copy link
Member

Choose a reason for hiding this comment

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

Instead let's use convenience functions as much as possible, like

_validate_type(token, (str, 'numeric'), 'token')

Then you can just do your try/except without any type business

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, to clarify, that would be:

try:
      _validate_type(token, (str, 'numeric'), 'token')
       return True
except ValueError:
       return False

?

Copy link
Member

Choose a reason for hiding this comment

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

Currently you raise an error for anything other than str, then try to convert to float and return True/False based on floatiness. So I guess it should be:

_validate_type(token, str, 'token')
try:
    float(token)
except ValueError:
    return False
else:
    return True

mne/io/eyelink/_utils.py Outdated Show resolved Hide resolved
mne/io/eyelink/_utils.py Outdated Show resolved Hide resolved
mne/io/eyelink/_utils.py Outdated Show resolved Hide resolved
@larsoner
Copy link
Member

... and as a general follow-up comment, please do look at the coverage after your next commit and take a look at the missed (red) lines and see if there is some way to get them covered.

I'll pull these down, make the remaining suggested changes, and test locally before pushing back up

Co-authored-by: Eric Larson <larson.eric.d@gmail.com>
@scott-huberty
Copy link
Contributor Author

... and as a general follow-up comment, please do look at the coverage after your next commit and take a look at the missed (red) lines and see if there is some way to get them covered.

Will do. I may need to add one or 2 more 1-second test files to mne-testing-data.

Also see my other comment about whether it would be worth taking another pass at this function (to see where things can be tightened up), as part of my GSoC.

@larsoner
Copy link
Member

I may need to add one or 2 more 1-second test files to mne-testing-data.

Another option if it's feasible -- since these are ASCII files right? -- is to just use tmp_path and read a test file line-by-line and write it to tmp_path line-by-line modifying (or adding or deleting) a few along the way to make files that would hit any problematic conditions. This avoids the need to add more test files to mne-testing-data and is usually plenty explicit. See for example here where we do it with a binary file (which is harder than ASCII I think):

# Test with number of records not in header (-1).
broken_fname = tmp_path / "broken.edf"
with open(edf_path, "rb") as fid_in:
fid_in.seek(0, 2)
n_bytes = fid_in.tell()
fid_in.seek(0, 0)
rbytes = fid_in.read()
with open(broken_fname, "wb") as fid_out:
fid_out.write(rbytes[:236])
fid_out.write(b"-1 ")
fid_out.write(rbytes[244 : 244 + int(n_bytes * 0.4)])
with pytest.warns(RuntimeWarning, match="records .* not match the file size"):
raw = read_raw_edf(broken_fname, preload=True)
read_raw_edf(broken_fname, exclude=raw.ch_names[:132], preload=True)

about whether it would be worth taking another pass at this function (to see where things can be tightened up), as part of my GSoC.

Yes code coverage and using better / more pythonic / more MNE-Pythonic coding practices would definitely fit! Maybe a good thing to do when you're waiting on any slow-to-respond devs to get around to reviewing your code :)

@larsoner
Copy link
Member

... so in other words you don't have to spend hours trying to improve coverage here. We can leave it as a longer-term goal on your TODO list somewhere

@scott-huberty
Copy link
Contributor Author

I may need to add one or 2 more 1-second test files to mne-testing-data.

Another option if it's feasible -- since these are ASCII files right? -- is to just use tmp_path and read a test file line-by-line and write it to tmp_path line-by-line modifying (or adding or deleting) a few along the way to make files that would hit any problematic conditions.

Sounds good I can give that a shot!

Yes code coverage and using better / more pythonic / more MNE-Pythonic coding practices would definitely fit! Maybe a good thing to do when you're waiting on any slow-to-respond devs to get around to reviewing your code :) ... We can leave it as a longer-term goal on your TODO list somewhere

Ha free time is always short but I I can start a separate branch and pick at it over the next few weeks. For now I'll take a pass and look for some low hanging fruit to increase the coverage. I can open a ticket regarding general improvements for read_raw_eyelink, with a goal to finish it at some point this summer!

@scott-huberty
Copy link
Contributor Author

@larsoner just a heads up, since all ocular annotations are channel aware now, I will remove the "eye" name from their description (i.e. all blink annotations will be BAD_blink now, instead of BAD_blink_L etc.).

- Will check codecov after this commit
…ead_raw_eyelink_annots

Merged main into branch. There was a conflict in doc/changes/latest, I resolved by moving my changes update below
The added line from the last PR on main (mathieus fix in 11748)
- now that the channel (and thus eye) name is in the ch_names key.
- the ocular annots can be uniform (i.e. alwasy BAD_blink, fixation, etc).
- These changes required some revisions to the tests
- removed some if-statement to error code, that has never been hit and shouldnt be hit
given the way the code is setting up the data structures before the line.
- added a test for apply_offsets
Can't replicate the failed test locally, could have be related to pandas development version
@larsoner
Copy link
Member

M1 failure is unrelated, thanks @scott-huberty !

@larsoner larsoner merged commit bad786d into mne-tools:main Jun 23, 2023
24 of 25 checks passed
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.

None yet

2 participants