Interactive version:
Processing monocular and binocular data¶
[1]:
%load_ext autoreload
%autoreload 2
import sys
sys.path.insert(0,"..")
import pypillometry as pp
import numpy as np
Data in pypillometry
can contain different variables
from different eyes
. The variables and eyes supported when importing raw data are
left_x
,right_x
(x-coordinate in screen coordinates from the eyetracker)left_y
,right_y
(y-coordinate in screen coordinates from the eyetracker)left_pupil
,right_pupil
(pupil size from left and right eye)
Depending on which class is chosen (PupilData
, GazeData
or EyeData
), some of these variables are required:
PupilData
: requires at least one ofleft_pupil
,right_pupil
(or both)GazeData
: requires at least one of(left_x, left_y)
and/or(right_x, right_y)
EyeData
: requiresx
,y
andpupil
from at least one eye
For example, let’s simulate some basic data:
[2]:
left_x = np.random.randn(1000)
right_x = np.random.randn(1000)
left_y = np.random.randn(1000)
right_y = np.random.randn(1000)
left_pupil = np.random.randn(1000)
right_pupil = np.random.randn(1000)
time = np.arange(1000)
# these are all ok
dpupil = pp.PupilData(left_pupil=left_pupil, right_pupil=right_pupil, time=time)
dgaze = pp.GazeData(left_x=left_x, left_y=left_y, right_x=right_x, right_y=right_y, time=time)
deye = pp.EyeData(left_x=left_x, left_y=left_y, left_pupil=left_pupil, time=time)
# these are not ok
#pp.PupilData(left_x=left_x, time=time)
#pp.GazeData(left_x=left_x, time=time)
pp.EyeData(left_x=left_x, time=time)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[2], line 17
12 deye = pp.EyeData(left_x=left_x, left_y=left_y, left_pupil=left_pupil, time=time)
14 # these are not ok
15 #pp.PupilData(left_x=left_x, time=time)
16 #pp.GazeData(left_x=left_x, time=time)
---> 17 pp.EyeData(left_x=left_x, time=time)
File ~/Dropbox/work/projects/pupil/pypillometry/docs/../pypillometry/eyedata/eyedata.py:96, in EyeData.__init__(self, time, left_x, left_y, left_pupil, right_x, right_y, right_pupil, event_onsets, event_labels, sampling_rate, screen_resolution, physical_screen_size, screen_eye_distance, name, fill_time_discontinuities, keep_orig, notes, inplace, use_cache, cache_dir, max_memory_mb)
94 logger.debug("Creating EyeData object")
95 if (left_x is None or left_y is None) and (right_x is None or right_y is None):
---> 96 raise ValueError("At least one of the eye-traces must be provided (both x and y)")
97 self.data=EyeDataDict(left_x=left_x, left_y=left_y, left_pupil=left_pupil,
98 right_x=right_x, right_y=right_y, right_pupil=right_pupil)
100 self._init_common(time, sampling_rate,
101 event_onsets, event_labels,
102 name, fill_time_discontinuities,
(...)
105 cache_dir=cache_dir,
106 max_memory_mb=max_memory_mb)
ValueError: At least one of the eye-traces must be provided (both x and y)
Once the data is loaded, we can check which variables and eyes are available using the .eyes
and .variables
attribute:
[ ]:
deye.eyes, deye.variables
(['left'], ['x', 'pupil', 'y'])
Simply printing an object will also show what data sources are available and give a glimpse into the data structure:
[3]:
deye
[3]:
EyeData(petokiga, 55.6KiB):
n : 1000
sampling_rate : 1000.0
data : ['left_x', 'left_y', 'left_pupil']
nevents : 0
screen_limits : not set
physical_screen_size: not set
screen_eye_distance : not set
duration_minutes : 0.016666666666666666
start_min : 0.0
end_min : 0.01665
parameters : {}
glimpse : EyeDataDict(vars=3,n=1000,shape=(1000,)):
left_x (float64): -0.05614305080280796, -0.6414526083279584, -1.704862857943198, 1.4798705854852807, 0.8472706708383512...
left_y (float64): -0.7499467879300401, 0.20023206315811148, 1.2681504964811687, -0.9232746614049664, -1.6396223281209275...
left_pupil (float64): 0.28284068252769706, -0.4969359675178975, -1.7109730132713936, -1.024197014557436, -0.7792117291273035...
eyes : ['left']
nblinks : {}
blinks : {'left': None}
params : {}
History:
*
└ fill_time_discontinuities()
[4]:
d = pp.get_example_data("rlmw_002_short")
d.variables, d.eyes
[4]:
(['pupil', 'y', 'x'], ['left', 'right'])
Almost all of pypillometry
’s functions have keyword arguments eyes=
and variables=
that specify which eyes/variables to operate on. By default, all of the variables and eyes are processed.
For example, we can run the scale()
function that will re-scale the data to have mean=0 and standard devation 1. Here, we use the context manager pp.loglevel("DEBUG")
to get output from pypillometry
internals:
[5]:
with pp.loglevel("DEBUG"):
deye.scale(eyes="left")
pp: 12:59:13 | DEBUG | _get_eye_var:194 | scale(): eyes=['left'], vars=['pupil', 'y', 'x']
pp: 12:59:13 | DEBUG | scale:820 | Mean: {'left': {'pupil': 0.0017743030582778934, 'y': -0.02958842274723986, 'x': 0.025062813034298286}}
pp: 12:59:13 | DEBUG | scale:821 | SD: {'left': {'pupil': 0.9685169385611292, 'y': 1.012354831683368, 'x': 0.9977838504196321}}
The output shows that all variables from the left eye have been processed.
Which functions work on which data?¶
Not all of pypillometry
s functions can be applied to all variables. Functions that are specific to pupil data have the prefix pupil_*
and functions that only work on gaze (x/y) data, have the prefix gaze_
. The other functions will operate on all variables (which may or may not make sense, it is up to you to check!).
Functions that work only on pupillometric data are implemented in the PupilData
class and are therefore not available when using GazeData
:
[32]:
dpupil.pupil_blinks_detect() # works fine
dgaze.pupil_blinks_detect() # fails
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[32], line 2
1 dpupil.pupil_blinks_detect() # works fine
----> 2 dgaze.pupil_blinks_detect()
AttributeError: 'GazeData' object has no attribute 'pupil_blinks_detect'
Functions that are implemented in GenericEyeData
will work for both:
[34]:
dpupil.scale() # works
dgaze.scale() # works
[34]:
GazeData(gememoda, 103.9KiB):
n : 1000
sampling_rate : 1000.0
data : ['left_x', 'left_y', 'right_x', 'right_y', 'mean_y', 'mean_x']
nevents : 0
screen_limits : not set
physical_screen_size: not set
screen_eye_distance : not set
duration_minutes : 0.016666666666666666
start_min : 0.0
end_min : 0.01665
parameters : {scale: {...}}
glimpse : EyeDataDict(vars=6,n=1000,shape=(1000,)):
left_x (float64): -0.0813862278918966, -0.6679957999740566, -1.7337679601147598, 1.458039004980029, 0.8240340404971094...
left_y (float64): -0.711567073755129, 0.22701574459144955, 1.2819012451103702, -0.8827796447335403, -1.5903849667971501...
right_x (float64): 0.9626973487677861, -0.17827876962019754, 0.1106137723940685, -0.9696903516215334, 0.6644680267943046...
right_y (float64): 0.31340189774712973, 1.1308736355320714, -0.7814852910860114, -1.2597233647796173, -0.3645995245346349...
mean_y (float64): -0.2900743012870026, 0.9746687313634781, 0.3671378610511327, -1.541330111846402, -1.4116343476312987...
mean_x (float64): 0.6144940257680912, -0.5963646337735499, -1.1476480335279726, 0.353192240734368, 1.0459758453244563...
History:
*
└ fill_time_discontinuities()
└ merge_eyes()
└ scale()
Creating new variables or eyes¶
In some cases, new variables or “eyes” can be created. For example, we might consider to reduce a binocular dataset to one where we average the timeseries from the two eyes. In that case, we can use function merge_eyes()
:
[6]:
dpupil.merge_eyes(eyes=["left", "right"], variables=["pupil"], method="mean")
[6]:
PupilData(debibiki, 56.3KiB):
n : 1000
sampling_rate : 1000.0
eyes : ['mean', 'left', 'right']
data : ['left_pupil', 'right_pupil', 'mean_pupil']
nevents : 0
nblinks : {}
blinks : {'mean': None, 'left': None, 'right': None}
duration_minutes: 0.016666666666666666
start_min : 0.0
end_min : 0.01665
params : {}
glimpse : EyeDataDict(vars=3,n=1000,shape=(1000,)):
left_pupil (float64): 0.28284068252769706, -0.4969359675178975, -1.7109730132713936, -1.024197014557436, -0.7792117291273035...
right_pupil (float64): 2.5061323051140176, 0.5500808555889285, -0.4194523189113897, 0.5384978966510412, -1.3899010906520874...
mean_pupil (float64): 1.3944864938208574, 0.026572444035515508, -1.0652126660913916, -0.24284955895319738, -1.0845564098896956...
History:
*
└ fill_time_discontinuities()
└ merge_eyes(eyes=['left', 'right'],variables=['pupil'],method=mean)
We can see that a new “eye” with variable “pupil” called mean_pupil
has been created. In this case, the original data left_pupil
and right_pupil
have been preserved (this can be changed by using keep_eyes=False
).
In other cases, the package can create new variables. For example, the function pupil_estimate_baseline()
will estimate tonic fluctuation in the pupil (see https://osf.io/preprints/psyarxiv/7ju4a_v2/) and will create a new variable <eye>_baseline
.
[16]:
d = pp.get_example_data("rlmw_002_short")
d.pupil_estimate_baseline()
d.variables
[16]:
['pupil', 'baseline', 'y', 'x']
Debugging¶
pupillometry
is taking, and which variables/eyes are being processed,pp.loglevel()
context manager to temporarily increase the logging level (the result is a rather lengthy and detailed debug-output):[28]:
with pp.loglevel("DEBUG"):
d.pupil_estimate_baseline()
pp: 13:12:00 | DEBUG | _get_eye_var:194 | pupil_estimate_baseline(): eyes=['left', 'right'], vars=['pupil', 'baseline', 'y', 'x']
pp: 13:12:00 | DEBUG | pupil_estimate_baseline:413 | Estimating baseline for eye left
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:198 | Downsampling factor is 50
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:208 | Downsampling done
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:214 | Peak-detection done, 42 peaks detected
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:217 | B-spline matrix built, dims=(410, 46)
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:228 | Compiling Stan model: /home/mmi041/Dropbox/work/projects/pupil/pypillometry/docs/../pypillometry/stan/baseline_model_asym_laplac.stan
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:250 | Optimizing Stan model
13:12:00 - cmdstanpy - INFO - Chain [1] start processing
13:12:00 - cmdstanpy - INFO - Chain [1] done processing
13:12:00 - cmdstanpy - WARNING - The default behavior of CmdStanVB.stan_variable() will change in a future release to return the variational sample, rather than the mean.
To maintain the current behavior, pass the argument mean=True
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:259 | Estimating PRF model (NNLS)
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:270 | 2nd Peak-detection done, 32 peaks detected
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:274 | 2nd B-spline matrix built, dims=(410, 36)
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:291 | Optimizing 2nd Stan model
13:12:00 - cmdstanpy - INFO - Chain [1] start processing
13:12:00 - cmdstanpy - INFO - Chain [1] done processing
13:12:00 - cmdstanpy - WARNING - The default behavior of CmdStanVB.stan_variable() will change in a future release to return the variational sample, rather than the mean.
To maintain the current behavior, pass the argument mean=True
pp: 13:12:00 | DEBUG | pupil_estimate_baseline:413 | Estimating baseline for eye right
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:198 | Downsampling factor is 50
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:208 | Downsampling done
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:214 | Peak-detection done, 42 peaks detected
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:217 | B-spline matrix built, dims=(410, 46)
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:228 | Compiling Stan model: /home/mmi041/Dropbox/work/projects/pupil/pypillometry/docs/../pypillometry/stan/baseline_model_asym_laplac.stan
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:250 | Optimizing Stan model
13:12:00 - cmdstanpy - INFO - Chain [1] start processing
13:12:00 - cmdstanpy - INFO - Chain [1] done processing
13:12:00 - cmdstanpy - WARNING - The default behavior of CmdStanVB.stan_variable() will change in a future release to return the variational sample, rather than the mean.
To maintain the current behavior, pass the argument mean=True
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:259 | Estimating PRF model (NNLS)
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:270 | 2nd Peak-detection done, 35 peaks detected
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:274 | 2nd B-spline matrix built, dims=(410, 39)
pp: 13:12:00 | DEBUG | baseline_envelope_iter_bspline:291 | Optimizing 2nd Stan model
13:12:00 - cmdstanpy - INFO - Chain [1] start processing
13:12:00 - cmdstanpy - INFO - Chain [1] done processing
13:12:00 - cmdstanpy - WARNING - The default behavior of CmdStanVB.stan_variable() will change in a future release to return the variational sample, rather than the mean.
To maintain the current behavior, pass the argument mean=True
Interactive version: