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

Function to convert eyegaze units to radians #12237

Merged
merged 33 commits into from
Feb 6, 2024

Conversation

scott-huberty
Copy link
Contributor

@scott-huberty scott-huberty commented Nov 24, 2023

closes #12112
closes #12391
This PR implements a function mne.preprocessing.eyetracking.convert_units to help users convert pixel data to radians, which is nice for us because radians are an SI unit, and it may come in handy down the line because converting pixel data to degrees/radians of visual angle is often done prior to calculating eye-tracking related derivatives (saccade amplitude, saccade velocity, etc).

Perhaps the best way to make sure this is behaving as expected is to plot a heatmap before and after unit conversion. But i'll need to refactor the mne.viz.eyetracking.plot_gaze to handle the scenario where data are in radians and not pixels.

@scott-huberty scott-huberty marked this pull request as draft November 24, 2023 17:48
- add requires_testing_data decorator
- add convert units API to doc
- fix mistake in calculation
- Make sure that plot_gaze works with eyegaze data that are in radians
- add demonstration of the above to current plot_gaze example
- add a bunch of tests to make sure no feature is covered
- created a raw_eyetrack fixture for reuse across tests
- add xref alias for calibration class
@scott-huberty
Copy link
Contributor Author

Hi @drammock , @britta-wstnr , @mscheltienne Happy new year!

I think this PR is ready for review. To recap previous discussions, the major concerns were:

    1. The conversion from on-screen pixel values to degrees/radians of visual angle is not a linear transformation.
    1. Converting between these units may yield different gaze-heatmaps/analytical results, particularly if the transformation is highly non-linear, leading to unexpected outcomes for users.

Consider simulating eyegaze data with fixations at three locations on a horizontal plane. The transformation becomes notably non-linear when the visual angle becomes too large (i.e the participant is too close to the screen or the screen is very large).

Pixels Degrees Degrees (too close to screen)

Fortunately, if the visual angle to the screen is within reasonable limits (< 30°), the relationship between pixel coordinates and visual angle is approximately linear (refer to the image below). Most eyetracker setups include a chin-rest to stabilize head position, and standard screens provided by manufacturers, so I think for most use cases the visual angle will be < 30° (but we could throw a warning if it is above this).

pixels-dva_relationship

Since reporting the visual angle is fairly common in eye-tracking literature, so I still think this is a useful feature to provide.

Updated tutorial with demonstration of unit conversion
Convert units API
updated plot_gaze API
get_screen_visual_angle API

@scott-huberty scott-huberty marked this pull request as ready for review January 17, 2024 22:02
doc/conf.py Outdated Show resolved Hide resolved
examples/visualization/eyetracking_plot_heatmap.py Outdated Show resolved Hide resolved
mne/preprocessing/eyetracking/eyetracking.py Show resolved Hide resolved
mne/preprocessing/eyetracking/eyetracking.py Outdated Show resolved Hide resolved
mne/preprocessing/eyetracking/eyetracking.py Outdated Show resolved Hide resolved
mne/preprocessing/eyetracking/eyetracking.py Show resolved Hide resolved
@larsoner
Copy link
Member

pip-pre failures are unrelated, I opened #12372 to track it

- ammend pre-commit yaml to see if it fixes strange formatting
- add warnings and info to convert_units docstring
- remove _1 from private pix_To_rad function name
- eyetraacking plot heatmap style suggestions

Co-authored-by: Eric Larson <[email protected]>
mne/preprocessing/eyetracking/eyetracking.py Outdated Show resolved Hide resolved
mne/preprocessing/eyetracking/eyetracking.py Outdated Show resolved Hide resolved
doc/conf.py Outdated Show resolved Hide resolved
.pre-commit-config.yaml Outdated Show resolved Hide resolved
doc/changes/devel/12237.newfeature.rst Outdated Show resolved Hide resolved
mne/conftest.py Outdated Show resolved Hide resolved
mne/conftest.py Outdated Show resolved Hide resolved
mne/preprocessing/eyetracking/eyetracking.py Outdated Show resolved Hide resolved
- wondering if there is just a precision error with ubuntu old CI, and if so, just pushing the visual angle value to be far above .52 (the threshold for warngin) will assure that the warning is thrown across all systems.
auto-merge was automatically disabled January 24, 2024 18:22

Head branch was pushed to by a user without write access

@scott-huberty
Copy link
Contributor Author

The failure actually looks related. If a warning is raised only sometimes, you can use with _record_warnings(): to ignore it

Hmm okay. I actually want the warning to be raised (AFAIK _record_warnings will just always ignore the warning without testing that it is raised like I want it to?)

I added a test that simulated a scenario where the unit conversion resulted in values greater than .52 rad (30 deg), so a warning should be thrown. The simulated rad values should be 1.10 so it can't be a matter of numerical precision.. Trying to figure it out but I'm not sure why the error isn't thrown on the Ubuntu old VM.

@larsoner
Copy link
Member

In this case I usually create a env in conda with the specs from the old env like:

conda create -n old python=3.9 numpy=1.21.2 scipy=1.7.1 matplotlib=3.5.0 pandas=1.3.2 scikit-learn=1.0 pip
conda activate old
pip install pytest pytest-cov

then you can hopefully run the tests locally to replicate the error and then inspect etc.

trying to figure out what is happening inside the ubuntu old runner causing the test to fail.. I will revert this commit
after i am done.
@scott-huberty
Copy link
Contributor Author

In this case I usually create a env in conda with the specs from the old env like:

conda create -n old python=3.9 numpy=1.21.2 scipy=1.7.1 matplotlib=3.5.0 pandas=1.3.2 scikit-learn=1.0 pip
conda activate old
pip install pytest pytest-cov

then you can hopefully run the tests locally to replicate the error and then inspect etc.

OK, unfortunately I can't replicate the test failiure locally on a Linux machine with the same specs as the ubuntu old env:

local environment specs
>>> mne.sys_info()
Platform             Linux-6.1.58+-x86_64-with-glibc2.31
Python               3.9.18 | packaged by conda-forge | (main, Dec 23 2023, 16:33:10)  [GCC 12.3.0]
Executable           /home/scotteric_hub/miniforge3/envs/old/bin/python
CPU                   (2 cores)
Memory               Unavailable (requires "psutil" package)
Core
├☑ mne               1.7.0.dev86+g4ccd30fed (devel, latest release is 1.6.1)
├☑ numpy             1.21.2 (OpenBLAS 0.3.25 with 2 threads)
├☑ scipy             1.7.1
├☑ matplotlib        3.5.0 (backend=agg)
├☑ pooch             1.8.0
└☑ jinja2            3.1.3

Numerical (optional)
├☑ sklearn           1.0.2
├☑ pandas            1.3.2
└☐ unavailable       numba, nibabel, nilearn, dipy, openmeeg, cupy

In 00ab0db I added a couple asserts to check that the values are what I expect during the test (they are), which should raise the warning.

So to me this suggests that the issue is with pytest or the warnings module. For example, pytest is looking for a UserWarning but maybe in the ubuntu old environment a RuntimeWarning is being thrown or something.. Just guessing... I can keep poking around but i'm not sure if it's worth the time for a warning. WDYT?

@scott-huberty
Copy link
Contributor Author

Ahah.. interesting. It looks like the warning we want is getting reported by pytest: https://github.com/mne-tools/mne-python/actions/runs/7644559198/job/20829160758?pr=12237#step:16:3509

but it isn't getting captured by our test line with pytest.raises(UserWarning, match="Some visual angle values"):

Copy link
Member

@larsoner larsoner left a comment

Choose a reason for hiding this comment

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

Yes feel free to sneak in a fix for #12391

mne/preprocessing/eyetracking/tests/test_eyetracking.py Outdated Show resolved Hide resolved
mne/preprocessing/eyetracking/tests/test_eyetracking.py Outdated Show resolved Hide resolved
mne/preprocessing/eyetracking/tests/test_eyetracking.py Outdated Show resolved Hide resolved
Copy link
Member

@britta-wstnr britta-wstnr left a comment

Choose a reason for hiding this comment

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

Sorry for being so late to the radians party ... Only found some typos, otherwise LGTM!

examples/visualization/eyetracking_plot_heatmap.py Outdated Show resolved Hide resolved
examples/visualization/eyetracking_plot_heatmap.py Outdated Show resolved Hide resolved
mne/preprocessing/eyetracking/utils.py Outdated Show resolved Hide resolved
@scott-huberty
Copy link
Contributor Author

all suggested revisions have been incorporated, are we okay to merge?

Copy link
Member

@larsoner larsoner left a comment

Choose a reason for hiding this comment

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

Just two tiny changes I pushed, marking for merge when green, thanks @scott-huberty !

Comment on lines +184 to +186
.. Important::
There are important considerations to keep in mind when using this function,
see the Notes section below.
Copy link
Member

Choose a reason for hiding this comment

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

@scott-huberty pushed a commit to add this to the top rather than have all the text indented in the Notes. It should make Notes more readable by itself and also call better attention to it by putting this at the top.

Comment on lines +235 to +236
if ch_dict["coil_type"] != FIFF.FIFFV_COIL_EYETRACK_POS:
continue
Copy link
Member

Choose a reason for hiding this comment

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

@scott-huberty this is the other change I made -- if not x: continue rather than if x: <indent everything another 4 spaces. I find it the same readability but less indented which is nice

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea thx!

@larsoner larsoner enabled auto-merge (squash) February 6, 2024 16:45
@larsoner larsoner merged commit acab264 into mne-tools:main Feb 6, 2024
28 checks passed
snwnde pushed a commit to snwnde/mne-python that referenced this pull request Mar 20, 2024
Co-authored-by: Eric Larson <[email protected]>
Co-authored-by: Daniel McCloy <[email protected]>
Co-authored-by: Britta Westner <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants