-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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: Create a Calibrations class for eyetracking data #11719
Conversation
…for eyelink calibration This commit creates a Calibrations class to store eyetracking calibration info. Calibrations subclasses list and contains a list of Calibration instances. Calibration instances sublcass OrderedDict.
Added read_eyelink_calibration function to mne namespace, because IMO thematically it fits in with other file I/O funcs like mne.read_epochs_eeglab, mne.read_evoked_besa, that are not mne.io.read_raw_[] funcs. Added read_eyelink_calibration to mne.docs.file_io.rst
After some deliberation, I believe that it is better to always return a Calibration instance for a single eye. This means that, for a recording in binocular mode, 2 Calibration instances will be returned for each "calibration" run during the session. For example if a single calibration was collected in binocular mode, the new API is just: Calibrations[0] for the left eye calibration, and Calibrations[1] for the right eye calibration. The two Calibration instances can be linked because they have the same exact onset. my reasoning is: Conceptual: even in binocular mode, a SINGLE calibration is done for each eye (though simultaneously). The calibration info for each eye is returned. API: Users can expect a consistent structure in the Calibration class. For example, calibrations[0]["avg_error"] will alwasy be a single float (where before, if binocular, it was a dict). Code Design: This makes the routines of read_eyelink_calibration much simpler, as we don't need a lot of conditional logic that depends on the recording mode being binocular or monocular.
Great, @scott-huberty ! Let me know once this is ready to be tested on additional data. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some small stuff my gaze tripped upon when looking at the PR - will do a more thorough review after tests pass 🙂
…eview. Instead of appending Calibration instances to a special Calibrations class, just append them to a list object. added Calibration class to mne namespace added Calibration class to doc/fileio.rst
Added a plot method to the Calibration class, and added a test for this method to mne.io.eyelink.tests.test_eyelink
That were suggested via the github browser.
This code simplifies the routine a bit, and makes it easier for a user to create a Calibration object manually (Now they would just pass in a list of tuples, instead of having to create a structured array).. Even though I think it is unlikely that someone will create one of these Calibration instances manually, it's good to make it easy if they do..
Thanks! since I made quite a bit of changes locally, I just made your suggested changes locally on top of my changes, (instead of comitting them here and rebasing). See the commit with title |
Maintenance note: After some refactoring and adding a |
The plot looks great, it could be nice to have the average and max error written in a corner. |
Good idea. I'll give it a shot! |
Removed read_eyelink_calibration, Calibration, from mne namespace moved Calibration.py to mne.preprocessing.eyetracking read_eyelink_calibration is accessed via mne.io namespace DOC: added Calibration to doc.preprocessing.rst DOC: added read_eyelink_calibration to reading_raw_data.rst
Hey @britta-wstnr and @larsoner : In my latest commit ec13a2b, I moved However, now when I try to import the
I trigger a circular import error, and I cannot figure out how to fix it! I pushed that commit to this PR knowing the tests will fail, so that I can share the circular import error: For example see this test failure |
I would nest the |
Co-authored-by: Eric Larson <[email protected]>
Co-authored-by: Eric Larson <[email protected]>
That did it, thanks! |
It seems like inherited methods of And this is triggering some In
@larsoner / @britta-wstnr do you have any idea of what I'm doing wrong here? |
Add |
... and also The short explanation is that warnings in doc build are treated as errors. So if you search "warning" in the circle log you'll see these. And we can ignore them. For the copy one it would actually be better to override the method using a deep copy like we do for Info and other objects that subclass |
Ok! Adding those phrases to What do you think we should do about the warning triggered by the move_to_end inherited method?:
|
Thanks @larsoner! I integrated your suggestions in 6ede0af and updated the docs / tests to reflect the changes. It looks like the CI's are good. Let me know when you've had a chance to continue your review |
Co-authored-by: Richard Höchenberger <[email protected]>
- described that we are deprecating the gap_description kwarg of read_raw_eyelink
Just FYI The Test windows 3.10 CI is returning an error "no matching distribution found for pandas" during the pip install action. Not sure if that's a fluke, Let me know if you want me to re-trigger the CI's. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there! Just some minor cleanups here and there then we should be good
@@ -338,16 +344,17 @@ def read_raw_eyelink( | |||
-------- | |||
mne.io.Raw : Documentation of attribute and methods. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add Notes
section mentioning that gaps between recording blocks will be annotated with BAD_ACQ_SKIP
mne/io/eyelink/eyelink.py
Outdated
@@ -418,7 +430,8 @@ class RawEyelink(BaseRaw): | |||
Whether whether a single eye was tracked ('monocular'), or both | |||
('binocular'). | |||
_gap_desc : str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No _
-prefixed attributes should be documented in the class doc. Feel free to move these to code comments if you want, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'll go through and remove these from the read_raw_eyelink
docstring.
|
||
def __repr__(self): | ||
"""Return a summary of the Calibration object.""" | ||
onset = self.get("onset", "N/A") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... then by making some (or all?) obligatory hopefully some of these can just be direct dict access within the f
-str rather than this self.get
business
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm yeah I see. Yeah, it's easy enough to make the values for most of those keys None
or np.nan
, and most of them are just informational and so having values of np.nan
or None
is not going to break anything.
Maybe making the 'positions'
, 'gaze'
, and 'offsets'
keys obligatory could be a little annoying for users who do have the 'model'
'avg_error'
and 'max_error'
information but don't have the data for individual points, in which case they would have to do something like np.empty(n_calibration_points, 2)
.
But I'm not sure yet how often someone will even create one of these instances from scratch instead of using an available helper function like read_eyelink_calibration
, so it's hard to say if it will be an issue for people?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's tell them not to create it from scratch and just make sure we do the right thing internally. This is how Forward, SourceSpaces, etc. have worked for the most part for years and it has been okay. So +1 for keeping as simple as possible and adding more checks later if needed
Co-authored-by: Eric Larson <[email protected]>
2 more of Eric's suggestions. 1 remaining that has not been added just yet. Co-authored-by: Eric Larson <[email protected]>
more suggestions from Eric Co-authored-by: Eric Larson <[email protected]>
- don't use custom __set_item__ dunder method - make most Calibration params positional - and now just use f string formatting in __repr__ - add notes section to read_raw_eyelink about bad_acq_skip - remove private attribute docstring from read_raw_eyelink - updated tests - updated doc because show_offsets defaults to true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one last idea then we can merge @scott-huberty !
Don't force onset to be 0, allow negative time if before recording start. Co-authored-by: Eric Larson <[email protected]>
I think I need to update the docstring to scrub any mention of "will be zero if calibration was before recording start". Plus looks like there is some style complaint. push a commit in a moment. |
@larsoner assuming the tests will run as smoothly here as they did locally, this is ready for your review again. Note that as discussed I added an |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, marking for merge-when-green once I eyeball the example (it looked good last I checked)! Thanks in advance @scott-huberty
* upstream/main: (24 commits) Allow int-like as ID of make_fixed_length_events (mne-tools#11748) Easycap-M43 montage (mne-tools#11744) ENH: Create a Calibrations class for eyetracking data (mne-tools#11719) Fix alphabetical order in overview/people.rst, fix sphinx formatting in docstrings and set verbose to keyword-only (mne-tools#11745) Add Mathieu Scheltienne to MNE-Python Steering Council (mne-tools#11741) removed requirement for curv.*h files to create Brain object (mne-tools#11704) [BUG] Fix mne.viz.Brain.add_volume_labels matrix ordering bug (mne-tools#11730) Fix installer links (mne-tools#11729) MAINT: Update for PyVista deprecation (mne-tools#11727) MAINT: Update roadmap (mne-tools#11724) MAINT: Update download link [skip azp] [skip cirrus] [skip actions] fix case for chpi_info[1] == None (mne-tools#11714) Add cmap argument for mne.viz.utils.plot_sensors (mne-tools#11720) BUG: Fix one more PySide6 bug (mne-tools#11723) MAINT: Fix PySide6 and PyVista compat (mne-tools#11721) MRG: If _check_fname() cannot find a file, display the path in quotation marks to help spot accidental trailing spaces (mne-tools#11718) Add "array-like" to `_validate_type()` (mne-tools#11713) MAINT: Avoid problematic PySide6 (mne-tools#11715) Fix installer links (mne-tools#11709) Updating change log after PR mne-tools#11575 (mne-tools#11707) ...
Yay for merging @scott-huberty ! 🎉 |
Reference issue
I forgot to open a ticket before creating a PR (apologies!):
This PR is based on my discussions with @larsoner and @britta-wstnr regarding my current GSoC project. For the first week, the goal was to extend functionality to reading and storing some meta-information from eye-tracking recordings, that will be needed for work down the line in this GSoC project.
What does this implement/fix?
This PR creates a
Calibration
class.Here is the updated eye-tracking tutorial.
Here is the documentation for the
Calibration
class.Here is the documentation for
read_eyelink_calibration
Here is the codecov report.
Minimally Working Example:
print(calibrations[0]) # show info for the left eye Calibration
Additional information
A couple of notes:
screen_size
,screen_distance
, andscreen_resolution
are not available in the ASCII files but can be optionally provided by the user.This draft from the WIP eye-tracking BIDS specificaton was used to guide some of the decisions regarding the fields to include by default in the
Calibration
class.TODOS:
codecov
of the changes introduce in this PRmne.io.eyelink.calibration
tomne.preprocessing.eyetrack.calibration
, since theCalibrations
class is not specific toeyelink
.plot
method for theCalibration
class.Calibration['points']
to see if it can be split into separatekeys
such that it is easier to work with / extend to future I/O functions for other manufacturers.