| Title: | Flexible, Extensible, & Reproducible Pupillometry Preprocessing |
|---|---|
| Description: | Pupillometry offers a non-invasive window into the mind and has been used extensively as a psychophysiological readout of arousal signals linked with cognitive processes like attention, stress, and emotional states [Clewett et al. (2020) <doi:10.1038/s41467-020-17851-9>; Kret & Sjak-Shie (2018) <doi:10.3758/s13428-018-1075-y>; Strauch (2024) <doi:10.1016/j.tins.2024.06.002>]. Yet, despite decades of pupillometry research, many established packages and workflows to date lack design patterns based on Findability, Accessibility, Interoperability, and Reusability (FAIR) principles [see Wilkinson et al. (2016) <doi:10.1038/sdata.2016.18>]. 'eyeris' provides a modular, performant, and extensible preprocessing framework for pupillometry data with BIDS-like organization and interactive output reports [Esteban et al. (2019) <doi:10.1038/s41592-018-0235-4>; Gorgolewski et al. (2016) <doi:10.1038/sdata.2016.44>]. Development was supported, in part, by the Stanford Wu Tsai Human Performance Alliance, Stanford Ric Weiland Graduate Fellowship, Stanford Center for Mind, Brain, Computation and Technology, NIH National Institute on Aging Grants (R01-AG065255, R01-AG079345), NSF GRFP (DGE-2146755), McKnight Brain Research Foundation Clinical Translational Research Scholarship in Cognitive Aging and Age-Related Memory Loss, American Brain Foundation, and the American Academy of Neurology. |
| Authors: | Shawn Schwartz [aut, cre] (ORCID: <https://orcid.org/0000-0001-6444-8451>), Mingjian He [ctb], Haopei Yang [ctb], Alice Xue [ctb], Gustavo Santiago-Reyes [ctb] |
| Maintainer: | Shawn Schwartz <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 3.3.0 |
| Built: | 2026-10-26 23:45:54 UTC |
| Source: | https://github.com/shawntz/eyeris |
This method provides a structured way to save out pupil data in a BIDS-like structure. The method saves out epoched data as well as the raw pupil time series, and formats the directory and filename structures based on the metadata you provide.
bidsify( eyeris, save_all = TRUE, epochs_list = NULL, bids_dir = NULL, participant_id = NULL, session_num = NULL, task_name = NULL, run_num = NULL, save_raw = TRUE, html_report = TRUE, report_seed = 0, report_epoch_grouping_var_col = "matched_event", verbose = TRUE, csv_enabled = TRUE, db_enabled = FALSE, db_path = "my-project", parallel_processing = FALSE, merge_epochs = deprecated(), merge_runs = deprecated(), pdf_report = deprecated() )bidsify( eyeris, save_all = TRUE, epochs_list = NULL, bids_dir = NULL, participant_id = NULL, session_num = NULL, task_name = NULL, run_num = NULL, save_raw = TRUE, html_report = TRUE, report_seed = 0, report_epoch_grouping_var_col = "matched_event", verbose = TRUE, csv_enabled = TRUE, db_enabled = FALSE, db_path = "my-project", parallel_processing = FALSE, merge_epochs = deprecated(), merge_runs = deprecated(), pdf_report = deprecated() )
eyeris |
An object of class |
save_all |
Logical flag indicating whether all epochs are to be saved
or only a subset of them. Defaults to |
epochs_list |
List of epochs to be saved. Defaults to |
bids_dir |
Base bids_directory. Defaults to |
participant_id |
BIDS subject ID. Defaults to |
session_num |
BIDS session ID. Defaults to |
task_name |
BIDS task ID. Defaults to |
run_num |
BIDS run ID. Optional override for the run number when there's
only one block of data present in a given |
save_raw |
Logical flag indicating whether to save_raw pupil data in
addition to epoched data. Defaults to |
html_report |
Logical flag indicating whether to save out the |
report_seed |
Random seed for the plots that will appear in the report
Defaults to |
report_epoch_grouping_var_col |
String name of grouping column to use
for epoch-by-epoch diagnostic plots in an interactive rendered HTML report.
Column name must exist (i.e., be a custom grouping variable name set within
the metadata template of your |
verbose |
A flag to indicate whether to print detailed logging messages.
Defaults to |
csv_enabled |
Logical flag indicating whether to write CSV output files.
Defaults to |
db_enabled |
Logical flag indicating whether to write data to a |
db_path |
Database filename or path. Defaults to |
parallel_processing |
Logical flag to manually enable parallel database
processing. When |
merge_epochs |
(Deprecated) This parameter is deprecated and will be ignored. All epochs are now saved as separate files following BIDS conventions. This parameter will be removed in a future version |
merge_runs |
(Deprecated) This parameter is deprecated and will be ignored. All runs are now saved as separate files following BIDS conventions. This parameter will be removed in a future version |
pdf_report |
(Deprecated) Use |
In the future, we intend for this function to save out the data in an official BIDS format for eyetracking data (see the proposal currently under review here). At this time, however, this function instead takes a more BIDS-inspired approach to organizing the output files for preprocessed pupil data.
Invisibly returns NULL. Called for its side effects
# bleed around blink periods just long enough to remove majority of # deflections due to eyelid movements demo_data <- eyelink_asc_demo_dataset() # example with unepoched data demo_data |> eyeris::glassbox() |> eyeris::bidsify( bids_dir = tempdir(), # <- MAKE SURE TO UPDATE TO YOUR DESIRED LOCAL PATH participant_id = "001", session_num = "01", task_name = "assocret", run_num = "01", save_raw = TRUE, # save out raw time series html_report = TRUE, # generate interactive report document report_seed = 0 # make randomly selected plot epochs reproducible ) # example with epoched data demo_data |> eyeris::glassbox() |> eyeris::epoch( events = "PROBE_{startstop}_{trial}", limits = c(-1, 1), # grab 1 second prior to and 1 second post event label = "prePostProbe" # custom epoch label name ) |> eyeris::bidsify( bids_dir = tempdir(), # <- MAKE SURE TO UPDATE TO YOUR DESIRED LOCAL PATH participant_id = "001", session_num = "01", task_name = "assocret", run_num = "01" ) # example with run_num for single block data demo_data <- eyelink_asc_demo_dataset() demo_data |> eyeris::glassbox() |> eyeris::epoch( events = "PROBE_{startstop}_{trial}", limits = c(-1, 1), label = "prePostProbe" ) |> eyeris::bidsify( bids_dir = tempdir(), participant_id = "001", session_num = "01", task_name = "assocret", run_num = "03" # override default run-01 (block_1) to use run-03 instead ) # example with database storage enabled demo_data |> eyeris::glassbox() |> eyeris::epoch( events = "PROBE_{startstop}_{trial}", limits = c(-1, 1), label = "prePostProbe" ) |> eyeris::bidsify( bids_dir = tempdir(), participant_id = "001", session_num = "01", task_name = "assocret", db_enabled = TRUE, # enable eyerisdb database storage db_path = "my-project" # custom project database name ) # example for large-scale cloud compute (database only, no CSV files) demo_data |> eyeris::glassbox() |> eyeris::bidsify( bids_dir = tempdir(), participant_id = "001", session_num = "01", task_name = "assocret", csv_enabled = FALSE, # disable CSV files db_enabled = TRUE # database storage only )# bleed around blink periods just long enough to remove majority of # deflections due to eyelid movements demo_data <- eyelink_asc_demo_dataset() # example with unepoched data demo_data |> eyeris::glassbox() |> eyeris::bidsify( bids_dir = tempdir(), # <- MAKE SURE TO UPDATE TO YOUR DESIRED LOCAL PATH participant_id = "001", session_num = "01", task_name = "assocret", run_num = "01", save_raw = TRUE, # save out raw time series html_report = TRUE, # generate interactive report document report_seed = 0 # make randomly selected plot epochs reproducible ) # example with epoched data demo_data |> eyeris::glassbox() |> eyeris::epoch( events = "PROBE_{startstop}_{trial}", limits = c(-1, 1), # grab 1 second prior to and 1 second post event label = "prePostProbe" # custom epoch label name ) |> eyeris::bidsify( bids_dir = tempdir(), # <- MAKE SURE TO UPDATE TO YOUR DESIRED LOCAL PATH participant_id = "001", session_num = "01", task_name = "assocret", run_num = "01" ) # example with run_num for single block data demo_data <- eyelink_asc_demo_dataset() demo_data |> eyeris::glassbox() |> eyeris::epoch( events = "PROBE_{startstop}_{trial}", limits = c(-1, 1), label = "prePostProbe" ) |> eyeris::bidsify( bids_dir = tempdir(), participant_id = "001", session_num = "01", task_name = "assocret", run_num = "03" # override default run-01 (block_1) to use run-03 instead ) # example with database storage enabled demo_data |> eyeris::glassbox() |> eyeris::epoch( events = "PROBE_{startstop}_{trial}", limits = c(-1, 1), label = "prePostProbe" ) |> eyeris::bidsify( bids_dir = tempdir(), participant_id = "001", session_num = "01", task_name = "assocret", db_enabled = TRUE, # enable eyerisdb database storage db_path = "my-project" # custom project database name ) # example for large-scale cloud compute (database only, no CSV files) demo_data |> eyeris::glassbox() |> eyeris::bidsify( bids_dir = tempdir(), participant_id = "001", session_num = "01", task_name = "assocret", csv_enabled = FALSE, # disable CSV files db_enabled = TRUE # database storage only )
This function bins pupillometry data by dividing time into equal intervals and averaging the data within each bin. Unlike downsampling, binning averages data points within each time bin.
bin(eyeris, bins_per_second, method = "mean", call_info = NULL)bin(eyeris, bins_per_second, method = "mean", call_info = NULL)
eyeris |
An object of class |
bins_per_second |
The number of bins to create per second of data |
method |
The binning method: "mean" (default) or "median" |
call_info |
A list of call information and parameters. If not provided,
it will be generated from the function call. Defaults to |
Binning divides one second of pupillary data into X bins and averages pupillometry data around each bin center. The resulting time points will be: 1/2X, 3/2X, 5/2X, ..., etc. where X is the number of bins per second.
This approach is commonly used in pupillometry research to study temporal dynamics of pupil dilatory response; however, it should be used with caution (as averaging within bins can distort the pupillary dynamics).
An eyeris object with binned data and updated sampling rate
This function is part of the glassbox() preprocessing pipeline and is not
intended for direct use in most cases. Provide parameters via
bin = list(...).
Advanced users may call it directly if needed.
glassbox() for the recommended way to run this step as
part of the full eyeris glassbox preprocessing pipeline
downsample() for downsampling functionality
For a complete, end-to-end reference pipeline that demonstrates how all
eyeris preprocessing functions are chained together in practice, see the
"Building Blocks Under the Hood" section of the Anatomy of an eyeris
Object vignette — vignette("anatomy", package = "eyeris") — as
well as the Complete Pupillometry Pipeline Walkthrough vignette:
vignette("complete-pipeline", package = "eyeris").
demo_data <- eyelink_asc_demo_dataset() # bin data into 10 bins per second using the (default) "mean" method demo_data |> eyeris::glassbox(bin = list(bins_per_second = 10, method = "mean")) |> # `preview_window` zooms in on a 2-second subset of the time series plot(seed = 0, preview_window = c(10, 12))demo_data <- eyelink_asc_demo_dataset() # bin data into 10 bins per second using the (default) "mean" method demo_data |> eyeris::glassbox(bin = list(bins_per_second = 10, method = "mean")) |> # `preview_window` zooms in on a 2-second subset of the time series plot(seed = 0, preview_window = c(10, 12))
Auto-generates human-readable, "fMRIPrep-style" methods-section boilerplate
text that describes – in plain prose – the exact preprocessing workflow
that was run on an eyeris object. Because every eyeris preprocessing step
records its own call stack and parameters (in eyeris$params), the pipeline
is able to self-document precisely what it did to your data, ready to paste
directly into the methods section of a manuscript.
This reinforces the workflow-level transparency at the heart of the eyeris
"glassbox" philosophy: the code that ran and the prose that describes it are
generated from one and the same source of truth.
boilerplate( eyeris, version = NULL, include_citation = TRUE, include_license = TRUE )boilerplate( eyeris, version = NULL, include_citation = TRUE, include_license = TRUE )
eyeris |
An object of class |
version |
Optional character string giving the |
include_citation |
Logical. Whether to append a formatted citation for
|
include_license |
Logical. Whether to append the Creative Commons CC BY
4.0 license note telling users it is safe to paste the text into a manuscript
as long as they cite |
The generated text walks the recorded pipeline steps in the canonical order
in which eyeris applies them (load_asc -> deblink -> detransient ->
interpolate -> lpfilt -> downsample/bin -> detrend -> zscore ->
epoch) and emits one sentence per step, substituting in the actual
parameter values that were used. Steps that were skipped are omitted, and any
custom (user-supplied) pipeline extensions are appended at the end so that
multi-step and non-default pipelines are described faithfully. Multi-block
(multi-run) and binocular recordings are handled automatically.
The boilerplate also references the per-run, machine-readable .json
metadata sidecar that eyeris writes for every run
(source/logs/run-XX_metadata.json), which together with the reported
package version is sufficient to reproduce the pipeline exactly.
License & attribution. The auto-generated boilerplate text is licensed
under the Creative Commons Attribution 4.0 International (CC BY 4.0) license
(https://creativecommons.org/licenses/by/4.0/). It is explicitly safe to
copy and paste the generated Markdown content directly into your manuscript,
provided that you give appropriate credit by citing eyeris (run
citation("eyeris") for the reference); including that citation in your
references satisfies the attribution requirement. This mirrors the approach
taken by fMRIPrep and other reproducible-pipeline tools.
When you run bidsify() (or glassbox() with reporting
enabled), this same boilerplate is embedded in the diagnostic HTML report and
written to derivatives/.../source/logs/ as a standalone Markdown file.
A length-one character string of Markdown-formatted methods
boilerplate (invisibly classed as eyeris_boilerplate so it prints nicely at
the console). The raw string can be passed to base::cat(),
base::writeLines(), or embedded directly in a report.
demo_data <- eyelink_asc_demo_dataset() eyeris_obj <- demo_data |> eyeris::glassbox(verbose = FALSE) # print the copy-and-paste-ready methods boilerplate boilerplate(eyeris_obj) # capture it as plain Markdown text (e.g., to write to a file) methods_md <- boilerplate(eyeris_obj) cat(methods_md)demo_data <- eyelink_asc_demo_dataset() eyeris_obj <- demo_data |> eyeris::glassbox(verbose = FALSE) # print the copy-and-paste-ready methods boilerplate boilerplate(eyeris_obj) # capture it as plain Markdown text (e.g., to write to a file) methods_md <- boilerplate(eyeris_obj) cat(methods_md)
Deblinking (a.k.a. NA-padding) of time series data. The intended use of this method is to remove blink-related artifacts surrounding periods of missing data. For instance, when an individual blinks, there are usually rapid decreases followed by increases in pupil size, with a chunk of data missing in-between these 'spike'-looking events. The deblinking procedure here will NA-pad each missing data point by your specified number of ms.
deblink(eyeris, extend = 50, call_info = NULL)deblink(eyeris, extend = 50, call_info = NULL)
eyeris |
An object of class |
extend |
Either a single number indicating the number of milliseconds to
pad forward/backward around each missing sample, or, a vector of length two
indicating different numbers of milliseconds pad forward/backward around each
missing sample, in the format |
call_info |
A list of call information and parameters. If not provided, it will be generated from the function call |
This function is automatically called by glassbox() by default. If needed,
customize the parameters for deblink by providing a parameter list. Use
glassbox(deblink = FALSE) to disable this step as needed.
Users should prefer using glassbox() rather than invoking this function
directly unless they have a specific reason to customize the pipeline
manually.
An eyeris object with a new column: pupil_raw_{...}_deblink
This function is part of the glassbox() preprocessing pipeline and is not
intended for direct use in most cases. Provide parameters via
deblink = list(...).
Advanced users may call it directly if needed.
glassbox() for the recommended way to run this step as
part of the full eyeris glassbox preprocessing pipeline
For a complete, end-to-end reference pipeline that demonstrates how all
eyeris preprocessing functions are chained together in practice, see the
"Building Blocks Under the Hood" section of the Anatomy of an eyeris
Object vignette — vignette("anatomy", package = "eyeris") — as
well as the Complete Pupillometry Pipeline Walkthrough vignette:
vignette("complete-pipeline", package = "eyeris").
demo_data <- eyelink_asc_demo_dataset() # 50 ms in both directions (the default) demo_data |> eyeris::glassbox(deblink = list(extend = 50)) |> # `preview_window` zooms in on a 2-second subset of the time series plot(seed = 0, preview_window = c(10, 12)) # 40 ms backward, 50 ms forward demo_data |> # set deblink to FALSE (instead of a list of params) # to skip step (not recommended) eyeris::glassbox(deblink = list(extend = c(40, 50))) |> plot(seed = 0, preview_window = c(10, 12))demo_data <- eyelink_asc_demo_dataset() # 50 ms in both directions (the default) demo_data |> eyeris::glassbox(deblink = list(extend = 50)) |> # `preview_window` zooms in on a 2-second subset of the time series plot(seed = 0, preview_window = c(10, 12)) # 40 ms backward, 50 ms forward demo_data |> # set deblink to FALSE (instead of a list of params) # to skip step (not recommended) eyeris::glassbox(deblink = list(extend = c(40, 50))) |> plot(seed = 0, preview_window = c(10, 12))
The intended use of this method is for removing pupil samples that emerge
more quickly than would be physiologically expected. This is accomplished by
rejecting samples that exceed a "speed"-based threshold (i.e., median
absolute deviation from sample-to-sample). This threshold is computed based
on the constant n, which defaults to the value 16.
detransient(eyeris, n = 16, mad_thresh = NULL, call_info = NULL)detransient(eyeris, n = 16, mad_thresh = NULL, call_info = NULL)
eyeris |
An object of class |
n |
A constant used to compute the median absolute deviation (MAD)
threshold. Defaults to |
mad_thresh |
Default
then, with the default multiplier
In this situation, any speed
|
call_info |
A list of call information and parameters. If not provided,
it will be generated from the function call. Defaults to |
This function is automatically called by glassbox() by default. If needed,
customize the parameters for detransient by providing a parameter list. Use
glassbox(detransient = FALSE) to disable this step as needed.
Users should prefer using glassbox() rather than invoking this function
directly unless they have a specific reason to customize the pipeline
manually.
Computed properties:
pupil_speed: Compute speed of pupil by approximating the derivative
of x (pupil) with respect to y (time) using finite differences.
Let and
be two numeric vectors with
; then, the finite differences are computed as:
This produces an output vector
defined by:
For the first element:
For the last element:
For the intermediate elements ():
median_speed: The median of the computed pupil_speed:
mad_val: The median absolute deviation (MAD) of pupil_speed
from the median:
mad_thresh: A threshold computed from the median speed and the MAD,
using a constant multiplier (default value: 16):
An eyeris object with a new column in time series:
pupil_raw_{...}_detransient
This function is part of the glassbox() preprocessing pipeline and is not
intended for direct use in most cases. Provide parameters via
detransient = list(...).
Advanced users may call it directly if needed.
glassbox() for the recommended way to run this step as
part of the full eyeris glassbox preprocessing pipeline.
For a complete, end-to-end reference pipeline that demonstrates how all
eyeris preprocessing functions are chained together in practice, see the
"Building Blocks Under the Hood" section of the Anatomy of an eyeris
Object vignette — vignette("anatomy", package = "eyeris") — as
well as the Complete Pupillometry Pipeline Walkthrough vignette:
vignette("complete-pipeline", package = "eyeris").
demo_data <- eyelink_asc_demo_dataset() demo_data |> eyeris::glassbox( detransient = list(n = 16) # set to FALSE to skip step (not recommended) ) |> # `preview_window` zooms in on a 2-second subset of the time series plot(seed = 0, preview_window = c(10, 12))demo_data <- eyelink_asc_demo_dataset() demo_data |> eyeris::glassbox( detransient = list(n = 16) # set to FALSE to skip step (not recommended) ) |> # `preview_window` zooms in on a 2-second subset of the time series plot(seed = 0, preview_window = c(10, 12))
Detrend pupil data by fitting a model of pupil_data ~ time and returning
the fitted values (the estimated trend) together with the residuals
(pupil_data - fitted_values). Two trend models are supported via the
method argument: "linear" (the default) removes a straight-line drift by
fitting an ordinary linear model, while "spline" removes a smooth,
potentially nonlinear drift by fitting a natural cubic spline basis of time
(splines::ns(time, df = spline_df)).
detrend( eyeris, method = c("linear", "spline"), spline_df = 5, call_info = NULL )detrend( eyeris, method = c("linear", "spline"), spline_df = 5, call_info = NULL )
eyeris |
An object of class |
method |
A string indicating the detrending model to fit. Either
|
spline_df |
The degrees of freedom for the natural cubic spline basis
used when |
call_info |
A list of call information and parameters. If not provided,
it will be generated from the function call. Defaults to |
This function is automatically called by glassbox() if detrend = TRUE.
Users should prefer using glassbox() rather than invoking this function
directly unless they have a specific reason to customize the pipeline
manually.
An eyeris object with two new columns in time series:
detrend_fitted_values, and pupil_raw_{...}_detrend
This function is part of the glassbox() preprocessing pipeline and is not
intended for direct use in most cases. Use glassbox(detrend = TRUE) for
linear detrending, or glassbox(detrend = list(method = "spline")) for
spline detrending.
Advanced users may call it directly if needed.
glassbox() for the recommended way to run this step as
part of the full eyeris glassbox preprocessing pipeline
For a complete, end-to-end reference pipeline that demonstrates how all
eyeris preprocessing functions are chained together in practice, see the
"Building Blocks Under the Hood" section of the Anatomy of an eyeris
Object vignette — vignette("anatomy", package = "eyeris") — as
well as the Complete Pupillometry Pipeline Walkthrough vignette:
vignette("complete-pipeline", package = "eyeris").
demo_data <- eyelink_asc_demo_dataset() # (a) linear detrending (the default) demo_data |> eyeris::glassbox(detrend = TRUE) |> # set to FALSE to skip step (default) # a wider `preview_window` makes the (slow) fitted trend easier to see plot(seed = 0, preview_window = c(5, 20)) # (b) spline detrending (removes a smooth, nonlinear trend) demo_data |> eyeris::glassbox(detrend = list(method = "spline", spline_df = 5)) |> plot(seed = 0, preview_window = c(5, 20))demo_data <- eyelink_asc_demo_dataset() # (a) linear detrending (the default) demo_data |> eyeris::glassbox(detrend = TRUE) |> # set to FALSE to skip step (default) # a wider `preview_window` makes the (slow) fitted trend easier to see plot(seed = 0, preview_window = c(5, 20)) # (b) spline detrending (removes a smooth, nonlinear trend) demo_data |> eyeris::glassbox(detrend = list(method = "spline", spline_df = 5)) |> plot(seed = 0, preview_window = c(5, 20))
This function downsamples pupillometry data by applying an anti-aliasing filter before decimation. Unlike binning, downsampling preserves the original temporal dynamics without averaging within bins.
downsample( eyeris, target_fs, plot_freqz = FALSE, rp = 1, rs = 35, call_info = NULL )downsample( eyeris, target_fs, plot_freqz = FALSE, rp = 1, rs = 35, call_info = NULL )
eyeris |
An object of class |
target_fs |
The target sampling frequency in Hz after downsampling. |
plot_freqz |
Boolean flag for displaying filter frequency response (default FALSE). |
rp |
Passband ripple in dB (default 1). |
rs |
Stopband attenuation in dB (default 35). |
call_info |
A list of call information and parameters. If not provided, it will be generated from the function call. |
Downsampling reduces the sampling frequency by decimating data points.
The function automatically designs an anti-aliasing filter using the
lpfilt() function with carefully chosen parameters:
ws (stopband frequency) = Fs_new / 2 (Nyquist freq of new sampling rate)
wp (passband frequency) = ws - max(5, Fs_nq * 0.2)
An error is raised if wp < 4 to prevent loss of pupillary responses
The resulting time points will be: 0, 1/X, 2/X, 3/X, ..., etc. where X is the new sampling frequency.
When the input contains gaps longer than the interpolation limit (see
interpolate()'s max_gap_ms) that were left as NA, those gaps
are temporarily filled so the anti-aliasing filter can run and then masked
back to NA. This can slightly bias the valid samples immediately adjacent
to each gap toward the interpolated values, so a warning is emitted in this
case. If that bias is a concern, consider disabling this step
(glassbox(downsample = FALSE)).
An eyeris object with downsampled data and updated sampling rate.
This function is part of the glassbox() preprocessing pipeline and is not
intended for direct use in most cases. Provide parameters via
downsample = list(...).
Advanced users may call it directly if needed.
glassbox() for the recommended way to run this step as
part of the full eyeris glassbox preprocessing pipeline.
bin() for binning functionality.
For a complete, end-to-end reference pipeline that demonstrates how all
eyeris preprocessing functions are chained together in practice, see the
"Building Blocks Under the Hood" section of the Anatomy of an eyeris
Object vignette — vignette("anatomy", package = "eyeris") — as
well as the Complete Pupillometry Pipeline Walkthrough vignette:
vignette("complete-pipeline", package = "eyeris").
demo_data <- eyelink_asc_demo_dataset() # downsample pupil data recorded at 1000 Hz to 100 Hz with the default params demo_data |> eyeris::glassbox(downsample = list(target_fs = 100)) |> # `preview_window` zooms in on a 2-second subset of the time series plot(seed = 0, preview_window = c(10, 12))demo_data <- eyelink_asc_demo_dataset() # downsample pupil data recorded at 1000 Hz to 100 Hz with the default params demo_data |> eyeris::glassbox(downsample = list(target_fs = 100)) |> # `preview_window` zooms in on a 2-second subset of the time series plot(seed = 0, preview_window = c(10, 12))
Intended to be used as the final preprocessing step. This function creates
data epochs of either fixed or dynamic durations with respect to provided
events and time limits, and also includes an intuitive metadata parsing
feature where additional trial data embedded within event messages can easily
be identified and joined into the resulting epoched data frames.
epoch( eyeris, events, limits = NULL, label = NULL, baseline = FALSE, baseline_type = c("sub", "div"), baseline_events = NULL, baseline_period = NULL, hz = NULL, verbose = TRUE, call_info = NULL, calc_baseline = deprecated(), apply_baseline = deprecated() )epoch( eyeris, events, limits = NULL, label = NULL, baseline = FALSE, baseline_type = c("sub", "div"), baseline_events = NULL, baseline_period = NULL, hz = NULL, verbose = TRUE, call_info = NULL, calc_baseline = deprecated(), apply_baseline = deprecated() )
eyeris |
An object of class |
events |
Either (1) a single string representing the event message to
perform trial extraction around, using specified list( data.frame(time = c(...), msg = c(...)), # start events data.frame(time = c(...), msg = c(...)), # end events 1 # block number ) where the first data.frame indicates the For event-modes |
limits |
A vector of 2 values (start, end) in seconds, indicating where
trial extraction should occur centered around any given |
label |
An (optional) string you can provide to customize the name of
the resulting |
baseline |
(New) A single parameter that controls baseline
correction. Set to |
baseline_type |
Whether to perform subtractive ( |
baseline_events |
Similar to |
baseline_period |
A vector of 2 values (start, end) in seconds,
indicating the window of data that will be used to perform the baseline
correction, which will be centered around the single string "start" message
string provided in |
hz |
Data sampling rate. If not specified, will use the value contained within the tracker's metadata |
verbose |
A flag to indicate whether to print detailed logging messages
Defaults to |
call_info |
A list of call information and parameters. If not provided, it will be generated from the function call |
calc_baseline |
(Deprecated) Use |
apply_baseline |
(Deprecated) Use |
An eyeris object with a new nested list of data frames: $epoch_*.
The epochs are organized hierarchically by block and preprocessing step.
Each epoch contains the pupil time series data for the specified time window
around each event message, along with metadata about the event.
When using bidsify() to export the data, filenames will include both
epoch and baseline event information for clarity.
demo_data <- eyelink_asc_demo_dataset() eye_preproc <- eyeris::glassbox(demo_data) # example 1: select 1 second before/after matched event message "PROBE*" eye_preproc |> eyeris::epoch(events = "PROBE*", limits = c(-1, 1)) # example 2: select all samples between each trial eye_preproc |> eyeris::epoch(events = "TRIALID {trial}") # example 3: grab the 1 second following probe onset eye_preproc |> eyeris::epoch( events = "PROBE_START_{trial}", limits = c(0, 1) ) # example 4: 1 second prior to and 1 second after probe onset eye_preproc |> eyeris::epoch( events = "PROBE_START_{trial}", limits = c(-1, 1), label = "prePostProbe" # custom epoch label name ) # example 5: manual start/end event pairs # note: here, the `msg` column of each data frame is optional eye_preproc |> eyeris::epoch( events = list( data.frame(time = c(11334491), msg = c("TRIALID 22")), # start events data.frame(time = c(11337158), msg = c("RESPONSE_22")), # end events 1 # block number ), label = "example5" ) # example 6: manual start/end event pairs # note: set `msg` to NA if you only want to pass in start/end timestamps eye_preproc |> eyeris::epoch( events = list( data.frame(time = c(11334491), msg = NA), # start events data.frame(time = c(11337158), msg = NA), # end events 1 # block number ), label = "example6" ) ## examples with baseline arguments enabled # example 7: use mean of 1-s preceding "PROBE_START" (i.e. "DELAY_STOP") # to perform subtractive baselining of the 1-s PROBE epochs. eye_preproc |> eyeris::epoch( events = "PROBE_START_{trial}", limits = c(0, 1), # grab 0 seconds prior to and 1 second post PROBE event label = "prePostProbe", # custom epoch label name baseline = TRUE, # calculate and apply baseline correction baseline_type = "sub", # "sub"tractive baseline calculation is default baseline_events = "DELAY_STOP_*", baseline_period = c(-1, 0) ) # example 8: use mean of time period between set start/end event messages # (i.e. between "DELAY_START" and "DELAY_STOP"). In this case, the # `baseline_period` argument will be ignored since both a "start" and "end" # message string are provided to the `baseline_events` argument. eye_preproc |> eyeris::epoch( events = "PROBE_START_{trial}", limits = c(0, 1), # grab 0 seconds prior to and 1 second post PROBE event label = "prePostProbe", # custom epoch label name baseline = TRUE, # calculate and apply baseline correction baseline_type = "sub", # "sub"tractive baseline calculation is default baseline_events = c( "DELAY_START_*", "DELAY_STOP_*" ) ) # example 9: additional (potentially helpful) example start_events <- data.frame( time = c(11334491, 11338691), msg = c("TRIALID 22", "TRIALID 23") ) end_events <- data.frame( time = c(11337158, 11341292), msg = c("RESPONSE_22", "RESPONSE_23") ) block_number <- 1 eye_preproc |> eyeris::epoch( events = list(start_events, end_events, block_number), label = "example9" )demo_data <- eyelink_asc_demo_dataset() eye_preproc <- eyeris::glassbox(demo_data) # example 1: select 1 second before/after matched event message "PROBE*" eye_preproc |> eyeris::epoch(events = "PROBE*", limits = c(-1, 1)) # example 2: select all samples between each trial eye_preproc |> eyeris::epoch(events = "TRIALID {trial}") # example 3: grab the 1 second following probe onset eye_preproc |> eyeris::epoch( events = "PROBE_START_{trial}", limits = c(0, 1) ) # example 4: 1 second prior to and 1 second after probe onset eye_preproc |> eyeris::epoch( events = "PROBE_START_{trial}", limits = c(-1, 1), label = "prePostProbe" # custom epoch label name ) # example 5: manual start/end event pairs # note: here, the `msg` column of each data frame is optional eye_preproc |> eyeris::epoch( events = list( data.frame(time = c(11334491), msg = c("TRIALID 22")), # start events data.frame(time = c(11337158), msg = c("RESPONSE_22")), # end events 1 # block number ), label = "example5" ) # example 6: manual start/end event pairs # note: set `msg` to NA if you only want to pass in start/end timestamps eye_preproc |> eyeris::epoch( events = list( data.frame(time = c(11334491), msg = NA), # start events data.frame(time = c(11337158), msg = NA), # end events 1 # block number ), label = "example6" ) ## examples with baseline arguments enabled # example 7: use mean of 1-s preceding "PROBE_START" (i.e. "DELAY_STOP") # to perform subtractive baselining of the 1-s PROBE epochs. eye_preproc |> eyeris::epoch( events = "PROBE_START_{trial}", limits = c(0, 1), # grab 0 seconds prior to and 1 second post PROBE event label = "prePostProbe", # custom epoch label name baseline = TRUE, # calculate and apply baseline correction baseline_type = "sub", # "sub"tractive baseline calculation is default baseline_events = "DELAY_STOP_*", baseline_period = c(-1, 0) ) # example 8: use mean of time period between set start/end event messages # (i.e. between "DELAY_START" and "DELAY_STOP"). In this case, the # `baseline_period` argument will be ignored since both a "start" and "end" # message string are provided to the `baseline_events` argument. eye_preproc |> eyeris::epoch( events = "PROBE_START_{trial}", limits = c(0, 1), # grab 0 seconds prior to and 1 second post PROBE event label = "prePostProbe", # custom epoch label name baseline = TRUE, # calculate and apply baseline correction baseline_type = "sub", # "sub"tractive baseline calculation is default baseline_events = c( "DELAY_START_*", "DELAY_STOP_*" ) ) # example 9: additional (potentially helpful) example start_events <- data.frame( time = c(11334491, 11338691), msg = c("TRIALID 22", "TRIALID 23") ) end_events <- data.frame( time = c(11337158, 11341292), msg = c("RESPONSE_22", "RESPONSE_23") ) block_number <- 1 eye_preproc |> eyeris::epoch( events = list(start_events, end_events, block_number), label = "example9" )
Returns the file path to the demo binocular .asc EyeLink pupil data file
included in the eyeris package.
eyelink_asc_binocular_demo_dataset()eyelink_asc_binocular_demo_dataset()
This dataset is a mock dataset trimmed from a larger data file. The original data file was obtained from: https://github.com/scott-huberty/eyelinkio/blob/main/src/eyelinkio/tests/data/test_raw_binocular.edf
A character string giving the full file path to the demo .asc
EyeLink pupil data file
path_to_binocular_demo_dataset <- eyelink_asc_binocular_demo_dataset() print(path_to_binocular_demo_dataset)path_to_binocular_demo_dataset <- eyelink_asc_binocular_demo_dataset() print(path_to_binocular_demo_dataset)
Returns the file path to the demo .asc EyeLink pupil data file
included in the eyeris package.
eyelink_asc_demo_dataset()eyelink_asc_demo_dataset()
A character string giving the full file path to the demo .asc
EyeLink pupil data file
path_to_demo_dataset <- eyelink_asc_demo_dataset() print(path_to_demo_dataset)path_to_demo_dataset <- eyelink_asc_demo_dataset() print(path_to_demo_dataset)
eyeris commands with automatic logging of R console's stdout and stderrThis utility function evaluates eyeris commands while automatically
capturing and recording both standard output (stdout) and standard error
(stderr) to timestamped log files in your desired log directory.
eyelogger( eyeris_cmd, log_dir = file.path(tempdir(), "eyeris_logs"), timestamp_format = "%Y%m%d_%H%M%S" )eyelogger( eyeris_cmd, log_dir = file.path(tempdir(), "eyeris_logs"), timestamp_format = "%Y%m%d_%H%M%S" )
eyeris_cmd |
An |
log_dir |
Character path to the desired log directory. Is set to the
temporary directory given by |
timestamp_format |
Format string passed to |
Each run produces two log files:
<timestamp>.out: records all console output
<timestamp>.err: records all warnings and errors
The result of the evaluated eyeris command (invisibly)
eyelogger({ message("eyeris `glassbox()` completed successfully.") warning("eyeris `glassbox()` completed with warnings.") print("some eyeris-related information.") }) eyelogger({ glassbox(eyelink_asc_demo_dataset(), interactive_preview = FALSE) }, log_dir = file.path(tempdir(), "eyeris_logs"))eyelogger({ message("eyeris `glassbox()` completed successfully.") warning("eyeris `glassbox()` completed with warnings.") print("some eyeris-related information.") }) eyelogger({ glassbox(eyelink_asc_demo_dataset(), interactive_preview = FALSE) }, log_dir = file.path(tempdir(), "eyeris_logs"))
A custom color palette designed for visualizing pupil data preprocessing steps. This palette is based on the RColorBrewer Set1 palette and provides distinct, visually appealing colors for different preprocessing stages.
eyeris_color_palette()eyeris_color_palette()
The palette includes 7 colors optimized for:
High contrast and visibility
Colorblind-friendly design
Consistent visual hierarchy across preprocessing steps
Professional appearance in reports and publications
Colors are designed to work well with both light and dark backgrounds and maintain readability when overlaid in time series plots.
A character vector of 7 hex color codes representing the default eyeris color palette
# get the default color palette colors <- eyeris_color_palette() print(colors) # use in a plot plot(1:7, 1:7, col = colors, pch = 19, cex = 3)# get the default color palette colors <- eyeris_color_palette() print(colors) # use in a plot plot(1:7, 1:7, col = colors, pch = 19, cex = 3)
A comprehensive wrapper function that simplifies extracting eyeris data from
the database. Provides easy one-liner access to aggregate data across multiple
subjects for each data type, without requiring SQL knowledge.
eyeris_db_collect( bids_dir, db_path = "my-project", subjects = NULL, data_types = NULL, sessions = NULL, tasks = NULL, epoch_labels = NULL, eye_suffixes = NULL, verbose = TRUE )eyeris_db_collect( bids_dir, db_path = "my-project", subjects = NULL, data_types = NULL, sessions = NULL, tasks = NULL, epoch_labels = NULL, eye_suffixes = NULL, verbose = TRUE )
bids_dir |
Path to the BIDS directory containing the database |
db_path |
Database name (defaults to "my-project", becomes "my-project.eyerisdb") |
subjects |
Vector of subject IDs to include. If NULL (default), includes all subjects |
data_types |
Vector of data types to extract. If NULL (default), extracts all available types. Valid types: "blinks", "events", "timeseries", "epochs", "epoch_summary", "run_confounds", "confounds_events", "confounds_summary" |
sessions |
Vector of session IDs to include. If NULL (default), includes all sessions |
tasks |
Vector of task names to include. If NULL (default), includes all tasks |
epoch_labels |
Vector of epoch labels to include. If NULL (default), includes all epochs. Only applies to epoch-related data types |
eye_suffixes |
Vector of eye suffixes to include. If NULL (default), includes all eyes. Typically c("eye-L", "eye-R") for binocular data |
verbose |
Logical. Whether to print progress messages (default TRUE) |
A named list of data frames, one per data type
demo_data <- eyelink_asc_demo_dataset() demo_data |> eyeris::glassbox() |> eyeris::epoch( events = "PROBE_{startstop}_{trial}", limits = c(-1, 1), label = "prePostProbe" ) |> eyeris::bidsify( bids_dir = tempdir(), participant_id = "001", session_num = "01", task_name = "assocret", run_num = "03", # override default run-01 (block_1) to use run-03 instead db_enabled = TRUE # enable database storage ) # extract all data for all subjects (returns list of data frames) all_data <- eyeris_db_collect(tempdir()) # view available data types names(all_data) # access specific data type blinks_data <- all_data$blinks epochs_data <- all_data$epochs # extract specific subjects and data types subset_data <- eyeris_db_collect( bids_dir = tempdir(), subjects = c("001"), data_types = c("blinks", "epochs", "timeseries") ) # extract epoch data for specific epoch label epoch_data <- eyeris_db_collect( bids_dir = tempdir(), data_types = "epochs", epoch_labels = "prepostprobe" )demo_data <- eyelink_asc_demo_dataset() demo_data |> eyeris::glassbox() |> eyeris::epoch( events = "PROBE_{startstop}_{trial}", limits = c(-1, 1), label = "prePostProbe" ) |> eyeris::bidsify( bids_dir = tempdir(), participant_id = "001", session_num = "01", task_name = "assocret", run_num = "03", # override default run-01 (block_1) to use run-03 instead db_enabled = TRUE # enable database storage ) # extract all data for all subjects (returns list of data frames) all_data <- eyeris_db_collect(tempdir()) # view available data types names(all_data) # access specific data type blinks_data <- all_data$blinks epochs_data <- all_data$epochs # extract specific subjects and data types subset_data <- eyeris_db_collect( bids_dir = tempdir(), subjects = c("001"), data_types = c("blinks", "epochs", "timeseries") ) # extract epoch data for specific epoch label epoch_data <- eyeris_db_collect( bids_dir = tempdir(), data_types = "epochs", epoch_labels = "prepostprobe" )
User-friendly function to connect to an existing eyeris project database.
This function provides easy access for users to query their eyeris data.
eyeris_db_connect(bids_dir, db_path = "my-project")eyeris_db_connect(bids_dir, db_path = "my-project")
bids_dir |
Path to the BIDS directory containing the database |
db_path |
Database name (defaults to "my-project", becomes "my-project.eyerisdb")
If just a filename, will look in |
Database connection object for use with other eyeris database functions
# step 1: create a database using bidsify with db_enabled = TRUE # (This example assumes you have already run bidsify to create a database) # temp dir for testing temp_dir <- tempdir() # step 2: connect to eyeris DB (will fail gracefully if no DB exists) tryCatch({ con <- eyeris_db_connect(temp_dir) tables <- eyeris_db_list_tables(con) # read timeseries data for a specific subject data <- eyeris_db_read(con, data_type = "timeseries", subject = "001") # close connection when done eyeris_db_disconnect(con) }, error = function(e) { message("No eyeris DB found - create one first with bidsify(db_enabled = TRUE)") })# step 1: create a database using bidsify with db_enabled = TRUE # (This example assumes you have already run bidsify to create a database) # temp dir for testing temp_dir <- tempdir() # step 2: connect to eyeris DB (will fail gracefully if no DB exists) tryCatch({ con <- eyeris_db_connect(temp_dir) tables <- eyeris_db_list_tables(con) # read timeseries data for a specific subject data <- eyeris_db_read(con, data_type = "timeseries", subject = "001") # close connection when done eyeris_db_disconnect(con) }, error = function(e) { message("No eyeris DB found - create one first with bidsify(db_enabled = TRUE)") })
User-friendly function to disconnect from the eyeris project database.
eyeris_db_disconnect(con)eyeris_db_disconnect(con)
con |
Database connection object |
Logical indicating success
Lists all tables in the eyeris project database with optional filtering.
eyeris_db_list_tables(con, data_type = NULL, subject = NULL)eyeris_db_list_tables(con, data_type = NULL, subject = NULL)
con |
Database connection |
data_type |
Optional filter by data type |
subject |
Optional filter by subject ID |
Character vector of table names
Reads eyeris data from the project database with dplyr-style interface.
eyeris_db_read( con, data_type = NULL, subject = NULL, session = NULL, task = NULL, run = NULL, eye_suffix = NULL, epoch_label = NULL, table_name = NULL )eyeris_db_read( con, data_type = NULL, subject = NULL, session = NULL, task = NULL, run = NULL, eye_suffix = NULL, epoch_label = NULL, table_name = NULL )
con |
Database connection |
data_type |
Type of data to read ("timeseries", "epochs", "epoch_timeseries", "epoch_summary", "events", "blinks") |
subject |
Optional subject ID filter |
session |
Optional session ID filter |
task |
Optional task name filter |
run |
Optional run number filter |
eye_suffix |
Optional eye suffix filter |
epoch_label |
Optional epoch label filter (for epoched data) |
table_name |
Exact table name (overrides other parameters) |
Data frame with requested data
Merges multiple chunked eyerisdb files back into a single database file.
Uses the reconstruction metadata file created by eyeris_db_split_for_sharing()
to ensure proper reconstruction.
eyeris_db_reconstruct_from_chunks( chunked_dir, output_path, reconstruction_file = NULL, verbose = TRUE )eyeris_db_reconstruct_from_chunks( chunked_dir, output_path, reconstruction_file = NULL, verbose = TRUE )
chunked_dir |
Directory containing the chunked database files and reconstruction metadata |
output_path |
Full path for the reconstructed database (e.g., "/path/to/reconstructed.eyerisdb") |
reconstruction_file |
Path to the reconstruction metadata JSON file. If NULL (default), searches for "*_reconstruction_info.json" in chunked_dir |
verbose |
Whether to print progress messages (default: TRUE) |
List containing information about the reconstruction process
## Not run: # Reconstruct database from chunked files reconstruction_info <- eyeris_db_reconstruct_from_chunks( chunked_dir = "/path/to/chunked_db/project-name", output_path = "/path/to/reconstructed-project.eyerisdb" ) # Specify custom reconstruction file location reconstruction_info <- eyeris_db_reconstruct_from_chunks( chunked_dir = "/path/to/chunked_db/project-name", output_path = "/path/to/reconstructed-project.eyerisdb", reconstruction_file = "/path/to/custom_reconstruction_info.json" ) ## End(Not run)## Not run: # Reconstruct database from chunked files reconstruction_info <- eyeris_db_reconstruct_from_chunks( chunked_dir = "/path/to/chunked_db/project-name", output_path = "/path/to/reconstructed-project.eyerisdb" ) # Specify custom reconstruction file location reconstruction_info <- eyeris_db_reconstruct_from_chunks( chunked_dir = "/path/to/chunked_db/project-name", output_path = "/path/to/reconstructed-project.eyerisdb", reconstruction_file = "/path/to/custom_reconstruction_info.json" ) ## End(Not run)
Creates multiple smaller eyerisdb files from a single large database for easier distribution via platforms with file size limits (GitHub, OSF, data repositories, etc.). Data can be chunked by data type, by number of chunks, or by maximum file size. Includes metadata to facilitate reconstruction of the original database.
eyeris_db_split_for_sharing( bids_dir, db_path = "my-project", output_dir = NULL, chunk_strategy = "by_data_type", n_chunks = 4, max_chunk_size_mb = 100, data_types = NULL, group_by_epoch_label = TRUE, include_metadata = TRUE, verbose = TRUE )eyeris_db_split_for_sharing( bids_dir, db_path = "my-project", output_dir = NULL, chunk_strategy = "by_data_type", n_chunks = 4, max_chunk_size_mb = 100, data_types = NULL, group_by_epoch_label = TRUE, include_metadata = TRUE, verbose = TRUE )
bids_dir |
Path to the BIDS directory containing the source database |
db_path |
Source database name (defaults to "my-project", becomes "my-project.eyerisdb") |
output_dir |
Directory to save chunked databases (defaults to bids_dir/derivatives/chunked_db) |
chunk_strategy |
Strategy for chunking: "by_data_type", "by_count", or "by_size" (default: "by_data_type") |
n_chunks |
Number of chunks to create when chunk_strategy = "by_count" (default: 4) |
max_chunk_size_mb |
Maximum size per chunk in MB when chunk_strategy = "by_size" (default: 100) |
data_types |
Vector of data types to include. If NULL (default), includes all available |
group_by_epoch_label |
If TRUE (default), processes epoch-related data types separately by epoch label |
include_metadata |
Whether to include eyeris metadata columns in chunked databases (default: TRUE) |
verbose |
Whether to print progress messages (default: TRUE) |
List containing information about created chunked databases and reconstruction instructions
## Not run: # These examples require an existing eyeris database # Chunk by data type (each data type gets its own database file) chunk_info <- eyeris_db_split_for_sharing( bids_dir = "/path/to/bids", db_path = "large-project", chunk_strategy = "by_data_type" ) # Chunk into 6 files by count chunk_info <- eyeris_db_split_for_sharing( bids_dir = "/path/to/bids", db_path = "large-project", chunk_strategy = "by_count", n_chunks = 6 ) # Chunk by size (max 50MB per file) chunk_info <- eyeris_db_split_for_sharing( bids_dir = "/path/to/bids", db_path = "large-project", chunk_strategy = "by_size", max_chunk_size_mb = 50 ) ## End(Not run)## Not run: # These examples require an existing eyeris database # Chunk by data type (each data type gets its own database file) chunk_info <- eyeris_db_split_for_sharing( bids_dir = "/path/to/bids", db_path = "large-project", chunk_strategy = "by_data_type" ) # Chunk into 6 files by count chunk_info <- eyeris_db_split_for_sharing( bids_dir = "/path/to/bids", db_path = "large-project", chunk_strategy = "by_count", n_chunks = 6 ) # Chunk by size (max 50MB per file) chunk_info <- eyeris_db_split_for_sharing( bids_dir = "/path/to/bids", db_path = "large-project", chunk_strategy = "by_size", max_chunk_size_mb = 50 ) ## End(Not run)
Provides a quick overview of the contents of an eyeris database,
including available subjects, sessions, tasks, and data types.
eyeris_db_summary(bids_dir, db_path = "my-project", verbose = TRUE)eyeris_db_summary(bids_dir, db_path = "my-project", verbose = TRUE)
bids_dir |
Path to the BIDS directory containing the database |
db_path |
Database name (defaults to "my-project", becomes "my-project.eyerisdb") |
verbose |
Logical. Whether to print detailed output (default TRUE) |
A named list containing summary information about the database contents
demo_data <- eyelink_asc_demo_dataset() demo_data |> eyeris::glassbox() |> eyeris::epoch( events = "PROBE_{startstop}_{trial}", limits = c(-1, 1), label = "prePostProbe" ) |> eyeris::bidsify( bids_dir = file.path(tempdir(), "my-cool-memory-project"), participant_id = "001", session_num = "01", task_name = "assocret", run_num = "03", # override default run-01 (block_1) to use run-03 instead db_enabled = TRUE, db_path = "my-cool-memory-study", ) # get database summary summary <- eyeris_db_summary( file.path( tempdir(), "my-cool-memory-project" ), db_path = "my-cool-memory-study" ) # view available subjects summary$subjects # view available data types summary$data_types # view table counts summary$table_countsdemo_data <- eyelink_asc_demo_dataset() demo_data |> eyeris::glassbox() |> eyeris::epoch( events = "PROBE_{startstop}_{trial}", limits = c(-1, 1), label = "prePostProbe" ) |> eyeris::bidsify( bids_dir = file.path(tempdir(), "my-cool-memory-project"), participant_id = "001", session_num = "01", task_name = "assocret", run_num = "03", # override default run-01 (block_1) to use run-03 instead db_enabled = TRUE, db_path = "my-cool-memory-study", ) # get database summary summary <- eyeris_db_summary( file.path( tempdir(), "my-cool-memory-project" ), db_path = "my-cool-memory-study" ) # view available subjects summary$subjects # view available data types summary$data_types # view table counts summary$table_counts
High-level wrapper function to export large eyeris databases to chunked CSV or Parquet files by data type. Uses chunked processing to handle very large datasets without memory issues.
eyeris_db_to_chunked_files( bids_dir, db_path = "my-project", output_dir = NULL, chunk_size = 1e+06, file_format = "csv", data_types = NULL, subjects = NULL, max_file_size_mb = 50, group_by_epoch_label = TRUE, verbose = TRUE )eyeris_db_to_chunked_files( bids_dir, db_path = "my-project", output_dir = NULL, chunk_size = 1e+06, file_format = "csv", data_types = NULL, subjects = NULL, max_file_size_mb = 50, group_by_epoch_label = TRUE, verbose = TRUE )
bids_dir |
Path to the BIDS directory containing the database |
db_path |
Database name (defaults to "my-project", becomes "my-project.eyerisdb") |
output_dir |
Directory to save output files (defaults to bids_dir/derivatives/eyerisdb_export) |
chunk_size |
Number of rows to process per chunk (default: 1000000) |
file_format |
Output format: "csv" or "parquet" (default: "csv") |
data_types |
Vector of data types to export. If NULL (default), exports all available |
subjects |
Vector of subject IDs to include. If NULL (default), includes all subjects |
max_file_size_mb |
Maximum file size in MB per output file (default: 50). When exceeded, automatically creates numbered files (e.g., data_01-of-03.csv, data_02-of-03.csv) |
group_by_epoch_label |
If TRUE (default), processes epoch-related data types separately by epoch label to reduce memory footprint and produce label-specific files. When FALSE, epochs with different labels are merged into single large files (not recommended). |
verbose |
Whether to print progress messages (default: TRUE) |
List containing information about exported files
## Not run: # These examples require an existing eyeris database # Export entire database to CSV files if (file.exists(file.path(tempdir(), "derivatives", "large-project.eyerisdb"))) { export_info <- eyeris_db_to_chunked_files( bids_dir = tempdir(), db_path = "large-project", chunk_size = 50000, file_format = "csv" ) } # Export specific data types to Parquet if (file.exists(file.path(tempdir(), "derivatives", "large-project.eyerisdb"))) { export_info <- eyeris_db_to_chunked_files( bids_dir = tempdir(), db_path = "large-project", data_types = c("timeseries", "events"), file_format = "parquet", chunk_size = 75000 ) } ## End(Not run)## Not run: # These examples require an existing eyeris database # Export entire database to CSV files if (file.exists(file.path(tempdir(), "derivatives", "large-project.eyerisdb"))) { export_info <- eyeris_db_to_chunked_files( bids_dir = tempdir(), db_path = "large-project", chunk_size = 50000, file_format = "csv" ) } # Export specific data types to Parquet if (file.exists(file.path(tempdir(), "derivatives", "large-project.eyerisdb"))) { export_info <- eyeris_db_to_chunked_files( bids_dir = tempdir(), db_path = "large-project", data_types = c("timeseries", "events"), file_format = "parquet", chunk_size = 75000 ) } ## End(Not run)
Utility function that takes an eyerisdb DuckDB database and splits it into N reasonably sized parquet files for easy management with GitHub, downloading, and distribution. Data is first grouped by table type (timeseries, epochs, events, etc.) since each has different columnar structures, then each group is split into the specified number of files. Files are organized in folders matching the database name for easy identification.
eyeris_db_to_parquet( bids_dir, db_path = "my-project", n_files_per_type = 1, output_dir = NULL, max_file_size = 512, data_types = NULL, verbose = TRUE, include_metadata = TRUE, epoch_labels = NULL, group_by_epoch_label = TRUE )eyeris_db_to_parquet( bids_dir, db_path = "my-project", n_files_per_type = 1, output_dir = NULL, max_file_size = 512, data_types = NULL, verbose = TRUE, include_metadata = TRUE, epoch_labels = NULL, group_by_epoch_label = TRUE )
bids_dir |
Path to the BIDS directory containing the database |
db_path |
Database name (defaults to "my-project", becomes "my-project.eyerisdb") |
n_files_per_type |
Number of parquet files to create per data type (default: 1) |
output_dir |
Directory to save parquet files (defaults to bids_dir/derivatives/parquet) |
max_file_size |
Maximum file size in MB per parquet file (default: 512) Used as a constraint when n_files_per_type would create files larger than this |
data_types |
Vector of data types to include. If NULL (default), includes all available. Valid types: "timeseries", "epochs", "epoch_summary", "events", "blinks", "confounds_*" |
verbose |
Whether to print progress messages (default: TRUE) |
include_metadata |
Whether to include eyeris metadata columns in output (default: TRUE) |
epoch_labels |
Optional character vector of epoch labels to include (e.g., "prepostprobe"). Only applies to epoch-related data types. If NULL, includes all labels. |
group_by_epoch_label |
If TRUE, processes epoch-related data types separately by epoch label to reduce memory footprint and produce label-specific parquet files (default: TRUE). |
List containing information about created parquet files
This function creates temporary tables during parquet export when the arrow package is not available. All temporary tables are automatically cleaned up, but if the process crashes, leftover tables may remain. The function checks for and warns about existing temporary tables before starting.
# create demo database demo_data <- eyelink_asc_demo_dataset() demo_data |> eyeris::glassbox() |> eyeris::epoch( events = "PROBE_{startstop}_{trial}", limits = c(-1, 1), label = "prePostProbe" ) |> eyeris::bidsify( bids_dir = tempdir(), participant_id = "001", session_num = "01", task_name = "memory", db_enabled = TRUE, db_path = "memory-task" ) # split into 3 parquet files per data type - creates memory-task/ folder split_info <- eyeris_db_to_parquet( bids_dir = tempdir(), db_path = "memory-task", n_files_per_type = 3 ) # split with size constraint and specific data types using the same database split_info <- eyeris_db_to_parquet( bids_dir = tempdir(), db_path = "memory-task", n_files_per_type = 5, max_file_size = 50, # max 50MB per file data_types = c("timeseries", "epochs", "events") )# create demo database demo_data <- eyelink_asc_demo_dataset() demo_data |> eyeris::glassbox() |> eyeris::epoch( events = "PROBE_{startstop}_{trial}", limits = c(-1, 1), label = "prePostProbe" ) |> eyeris::bidsify( bids_dir = tempdir(), participant_id = "001", session_num = "01", task_name = "memory", db_enabled = TRUE, db_path = "memory-task" ) # split into 3 parquet files per data type - creates memory-task/ folder split_info <- eyeris_db_to_parquet( bids_dir = tempdir(), db_path = "memory-task", n_files_per_type = 3 ) # split with size constraint and specific data types using the same database split_info <- eyeris_db_to_parquet( bids_dir = tempdir(), db_path = "memory-task", n_files_per_type = 5, max_file_size = 50, # max 50MB per file data_types = c("timeseries", "epochs", "events") )
eyeris pipelineThis glassbox function (in contrast to a "black box" function where you run
it and get a result but have no (or little) idea as to how you got from input
to output) has a few primary benefits over calling each exported function
from eyeris separately.
glassbox( file, interactive_preview = FALSE, preview_n = 3, preview_duration = 5, preview_window = NULL, verbose = TRUE, ..., confirm = deprecated(), num_previews = deprecated(), detrend_data = deprecated(), skip_detransient = deprecated() )glassbox( file, interactive_preview = FALSE, preview_n = 3, preview_duration = 5, preview_window = NULL, verbose = TRUE, ..., confirm = deprecated(), num_previews = deprecated(), detrend_data = deprecated(), skip_detransient = deprecated() )
file |
Either an SR Research EyeLink |
interactive_preview |
A flag to indicate whether to run the |
preview_n |
Number of random example "epochs" to generate for previewing the effect of each preprocessing step on the pupil time series |
preview_duration |
Time in seconds of each randomly selected preview |
preview_window |
The start and stop raw timestamps used to subset the
preprocessed data from each step of the |
verbose |
A logical flag to indicate whether to print status messages to
the console. Defaults to |
... |
Additional arguments to override the default, prescribed settings |
confirm |
(Deprecated) Use |
num_previews |
(Deprecated) Use |
detrend_data |
(Deprecated) A flag to indicate whether to run the
|
skip_detransient |
(Deprecated) A flag to indicate whether to skip
the |
First, this glassbox function provides a highly opinionated prescription of
steps and starting parameters we believe any pupillometry researcher should
use as their defaults when preprocessing pupillometry data.
Second, and not mutually exclusive from the first point, using this function should ideally reduce the probability of accidental mishaps when "reimplementing" the steps from the preprocessing pipeline both within and across projects. We hope to streamline the process in such a way that you could collect a pupillometry dataset and within a few minutes assess the quality of those data while simultaneously running a full preprocessing pipeline in 1-ish line of code!
Third, glassbox provides an "interactive" framework where you can evaluate
the consequences of the parameters within each step on your data in real
time, facilitating a fairly easy-to-use workflow for parameter optimization
on your particular dataset. This process essentially takes each of the
opinionated steps and provides a pre-/post-plot of the time series data for
each step so you can adjust parameters and re-run the pipeline until you are
satisfied with the choices of your parameters and their consequences on your
pupil time series data.
Preprocessed pupil data contained within an object of class eyeris
glassbox() does, step by stepIn plain language, glassbox() runs the following eyeris steps in order on
your pupil time series. Steps marked (default: on) run automatically;
steps marked (default: off) are skipped unless you explicitly enable
them. Most preprocessing steps can be turned off by passing <step> = FALSE
(except load_asc, which always runs when file is an .asc path). Steps
that accept parameters can be
customized by passing <step> = list(...) with values you want to override
(for example, deblink = list(extend = 40)).
Load the data (load_asc, default: on) – Reads and parses the
EyeLink .asc file into an eyeris object, automatically splitting the
recording into blocks and, for binocular recordings, handling each eye
separately. See load_asc(). This step is skipped when a pre-loaded
eyeris object is passed as file (see the file parameter).
Resample onto a uniform grid (resample, default: on) – Places each
block on the expected uniform sampling grid. For hardware that drops
samples (instead of zero-filling) when pupil data is missing, this
interpolates local sub-period timing jitter and inserts NA rows at the
dropped timestamps, so the rate-dependent steps that follow stay valid. A
guaranteed no-op for already-uniform data (e.g., EyeLink). See
resample().
Remove blinks (deblink, default: on) – Replaces the missing data
around blinks with NAs, extending each gap by 50 ms on either side so
that the rapid dips and spikes that surround a blink are removed too. See
deblink().
Remove transient artifacts (detransient, default: on) – Rejects
pupil samples that change faster than is physiologically plausible, using a
speed-based median absolute deviation (MAD) threshold. See
detransient().
Interpolate missing samples (interpolate, default: on) – Fills the
NA gaps left by the resample, deblink, and detransient steps using linear
interpolation, producing a continuous, gap-free time series. See
interpolate().
Smooth the signal (lpfilt, default: on) – Applies a low-pass
filter (default 4 Hz passband) to remove high-frequency noise while
preserving the slower pupil dynamics of interest. See lpfilt().
Downsample (downsample, default: off) – Optionally lowers the
sampling rate using an anti-aliasing filter, which preserves the temporal
dynamics of the signal. Cannot be combined with bin. See
downsample().
Bin (bin, default: off) – Optionally lowers the sampling rate by
averaging samples within equal-width time bins. Cannot be combined with
downsample. See bin().
Detrend (detrend, default: off) – Optionally fits a model of
pupil ~ time and returns the residuals (along with the fitted trend) to
remove slow drift. By default (detrend = TRUE) a straight-line
(method = "linear") trend is removed; pass
detrend = list(method = "spline", spline_df = 5) to instead remove a
smooth, potentially nonlinear trend via a natural cubic spline of time. Use
with care – see detrend() for when this is appropriate.
Z-score (zscore, default: on) – Rescales the pupil time series to a
mean of 0 and a standard deviation of 1, making values comparable across
participants and recordings. See zscore().
After preprocessing, glassbox() calls summarize_confounds() to compute
per-step confound metrics (e.g., missingness and gaze statistics) and store them
in $confounds.
Crucially, each step adds a new column to the time series rather than
overwriting the previous one, so every intermediate stage is preserved inside
the returned eyeris object. This is what makes the pipeline a "glass box":
you can inspect, plot, and compare the data before and after each
transformation (for example, plot(output, steps = c(1, 5))).
demo_data <- eyelink_asc_demo_dataset() # (1) examples using the default prescribed parameters and pipeline recipe ## (a) run an automated pipeline with no real-time inspection of parameters output <- eyeris::glassbox(demo_data) start_time <- min(output$timeseries$block_1$time_secs) end_time <- max(output$timeseries$block_1$time_secs) # by default, verbose = TRUE. To suppress messages, set verbose = FALSE. plot( output, steps = c(1, 5), preview_window = c(start_time, end_time), seed = 0 ) ## (b) run a interactive workflow (with confirmation prompts after each step) output <- eyeris::glassbox(demo_data, interactive_preview = TRUE, seed = 0) # (2) examples of overriding the default parameters output <- eyeris::glassbox( demo_data, interactive_preview = FALSE, # TRUE to visualize each step in real-time deblink = list(extend = 40), # only interpolate gaps up to 100 ms; longer gaps are left as NA interpolate = list(max_gap_ms = 100), lpfilt = list(plot_freqz = TRUE) # overrides verbose parameter ) # to suppress messages, set verbose = FALSE in plot(): plot(output, seed = 0, verbose = FALSE, preview_window = c(10, 12)) # (3) examples of disabling certain steps output <- eyeris::glassbox( demo_data, detransient = FALSE, detrend = FALSE, zscore = FALSE ) plot(output, seed = 0, preview_window = c(10, 12))demo_data <- eyelink_asc_demo_dataset() # (1) examples using the default prescribed parameters and pipeline recipe ## (a) run an automated pipeline with no real-time inspection of parameters output <- eyeris::glassbox(demo_data) start_time <- min(output$timeseries$block_1$time_secs) end_time <- max(output$timeseries$block_1$time_secs) # by default, verbose = TRUE. To suppress messages, set verbose = FALSE. plot( output, steps = c(1, 5), preview_window = c(start_time, end_time), seed = 0 ) ## (b) run a interactive workflow (with confirmation prompts after each step) output <- eyeris::glassbox(demo_data, interactive_preview = TRUE, seed = 0) # (2) examples of overriding the default parameters output <- eyeris::glassbox( demo_data, interactive_preview = FALSE, # TRUE to visualize each step in real-time deblink = list(extend = 40), # only interpolate gaps up to 100 ms; longer gaps are left as NA interpolate = list(max_gap_ms = 100), lpfilt = list(plot_freqz = TRUE) # overrides verbose parameter ) # to suppress messages, set verbose = FALSE in plot(): plot(output, seed = 0, verbose = FALSE, preview_window = c(10, 12)) # (3) examples of disabling certain steps output <- eyeris::glassbox( demo_data, detransient = FALSE, detrend = FALSE, zscore = FALSE ) plot(output, seed = 0, preview_window = c(10, 12))
Linear interpolation of time series data. The intended use of this method
is for filling in missing pupil samples (NAs) in the time series. This method
uses "na.approx()" function from the zoo package, which implements linear
interpolation using the "approx()" function from the stats package.
Currently, NAs at the beginning and the end of the data are replaced with
values on either end, respectively, using the "rule = 2" argument in the
approx() function.
interpolate(eyeris, max_gap_ms = 250, verbose = TRUE, call_info = NULL)interpolate(eyeris, max_gap_ms = 250, verbose = TRUE, call_info = NULL)
eyeris |
An object of class |
max_gap_ms |
The maximum duration (in milliseconds) of a gap of missing
( |
verbose |
A flag to indicate whether to print detailed logging messages.
Defaults to |
call_info |
A list of call information and parameters. If not provided, it will be generated from the function call |
By default, only gaps shorter than or equal to max_gap_ms milliseconds are
interpolated. Any gap longer than this threshold is left as NA rather than
being interpolated over. This follows the recommendation of Kret &
Sjak-Shie (2018) to avoid interpolating across long stretches of missing
data, where linear interpolation is unlikely to reflect the true underlying
pupil signal. The default of 250 ms matches the value used in that paper.
Set max_gap_ms = Inf (or NULL) to disable the limit and interpolate
across all gaps, restoring the behavior of eyeris versions <= 3.2.0.
Downstream glassbox() steps that cannot operate on missing data (low-pass
filtering, downsampling, and binning) automatically work around these
retained gaps – filtering/resampling over a temporarily filled copy and
then restoring the gaps as NA – so the gaps are preserved through to the
final preprocessed output. Because of this temporary fill, the filtering
steps (lpfilt() and the anti-aliasing filter in downsample()) can
slightly bias the valid samples immediately adjacent to a long retained gap
toward the interpolated values. These steps therefore emit a warning when
they operate over such gaps, so you can choose to disable them (e.g.
lpfilt = FALSE and/or downsample = FALSE in glassbox()) if this bias is
a concern for your analysis.
Note: Prior to eyeris version 3.3.0, all gaps were interpolated
regardless of duration. Enforcing max_gap_ms is a change in default
behavior and may affect downstream results.
This function is automatically called by glassbox() by default. Use
glassbox(interpolate = FALSE) to disable this step as needed.
Users should prefer using glassbox() rather than invoking this function
directly unless they have a specific reason to customize the pipeline
manually.
An eyeris object with a new column in timeseries:
pupil_raw_{...}_interpolate
This function is part of the glassbox() preprocessing pipeline and is not
intended for direct use in most cases. Use glassbox(interpolate = TRUE),
or provide parameters via glassbox(interpolate = list(max_gap_ms = ...)).
Advanced users may call it directly if needed.
Kret, M. E., & Sjak-Shie, E. E. (2018). Preprocessing pupil size data: Guidelines and code. Behavior Research Methods, 51(3), 1336-1342. doi:10.3758/s13428-018-1075-y
glassbox() for the recommended way to run this step as
part of the full eyeris glassbox preprocessing pipeline.
For a complete, end-to-end reference pipeline that demonstrates how all
eyeris preprocessing functions are chained together in practice, see the
"Building Blocks Under the Hood" section of the Anatomy of an eyeris
Object vignette — vignette("anatomy", package = "eyeris") — as
well as the Complete Pupillometry Pipeline Walkthrough vignette:
vignette("complete-pipeline", package = "eyeris").
demo_data <- eyelink_asc_demo_dataset() demo_data |> # set to FALSE to skip (not recommended) eyeris::glassbox(interpolate = TRUE) |> # `preview_window` zooms in on a 2-second subset of the time series plot(seed = 0, preview_window = c(10, 12)) # only interpolate gaps up to 100 ms; leave longer gaps as NA demo_data |> eyeris::glassbox(interpolate = list(max_gap_ms = 100)) |> plot(seed = 0, preview_window = c(10, 12))demo_data <- eyelink_asc_demo_dataset() demo_data |> # set to FALSE to skip (not recommended) eyeris::glassbox(interpolate = TRUE) |> # `preview_window` zooms in on a 2-second subset of the time series plot(seed = 0, preview_window = c(10, 12)) # only interpolate gaps up to 100 ms; leave longer gaps as NA demo_data |> eyeris::glassbox(interpolate = list(max_gap_ms = 100)) |> plot(seed = 0, preview_window = c(10, 12))
.asc filesThis function builds upon the eyelinker::read.asc() function to parse the
messages and metadata within the EyeLink .asc file. After loading and
additional processing, this function returns an S3 eyeris class for use in
all subsequent eyeris pipeline steps and functions.
load_asc( file, block = "auto", binocular_mode = c("average", "left", "right", "both"), verbose = TRUE )load_asc( file, block = "auto", binocular_mode = c("average", "left", "right", "both"), verbose = TRUE )
file |
An SR Research EyeLink |
block |
Optional block number specification. The following are valid options:
|
binocular_mode |
Optional binocular mode specification. The following are valid options:
|
verbose |
Logical. Whether to print verbose output (default TRUE). |
This function is automatically called by glassbox() by default. If
needed, customize the parameters for load_asc by providing a parameter
list.
Users should prefer using glassbox() rather than invoking this
function directly unless they have a specific reason to customize the
pipeline manually.
An object of S3 class eyeris with the following attributes:
file: Path to the original .asc file.
timeseries: Data frame of all raw time series data from the
tracker.
events: Data frame of all event messages and their time
stamps.
blinks: Data frame of all blink events.
info: Data frame of various metadata parsed from the file
header.
latest: eyeris variable for tracking pipeline run history.
For binocular data with binocular_mode = "both", returns a list
containing:
left: An eyeris object for the left eye data.
right: An eyeris object for the right eye data.
original_file: Path to the original .asc file.
This function is part of the glassbox() preprocessing pipeline and is not
intended for direct use in most cases. Provide parameters via
load_asc = list(...).
Advanced users may call it directly if needed.
eyelinker::read.asc() which this function wraps.
glassbox() for the recommended way to run this step
as part of the full eyeris glassbox preprocessing pipeline.
For a complete, end-to-end reference pipeline that demonstrates how all
eyeris preprocessing functions are chained together in practice, see the
"Building Blocks Under the Hood" section of the Anatomy of an eyeris
Object vignette — vignette("anatomy", package = "eyeris") — as
well as the Complete Pupillometry Pipeline Walkthrough vignette:
vignette("complete-pipeline", package = "eyeris").
demo_data <- eyelink_asc_demo_dataset() demo_data |> eyeris::glassbox(load_asc = list(block = 1)) # Other useful parameter configurations ## (1) Basic usage (no block column specified) demo_data |> eyeris::load_asc() ## (2) Manual specification of block number demo_data |> eyeris::load_asc(block = 3) ## (3) Auto-detect multiple recording segments embedded within the same ## file (i.e., the default behavior) demo_data |> eyeris::load_asc(block = "auto") ## (4) Omit block column demo_data |> eyeris::load_asc(block = NULL)demo_data <- eyelink_asc_demo_dataset() demo_data |> eyeris::glassbox(load_asc = list(block = 1)) # Other useful parameter configurations ## (1) Basic usage (no block column specified) demo_data |> eyeris::load_asc() ## (2) Manual specification of block number demo_data |> eyeris::load_asc(block = 3) ## (3) Auto-detect multiple recording segments embedded within the same ## file (i.e., the default behavior) demo_data |> eyeris::load_asc(block = "auto") ## (4) Omit block column demo_data |> eyeris::load_asc(block = NULL)
Construct a valid eyeris S3 object from standardized data frames so that
data from eye trackers other than SR Research EyeLink can enter the
eyeris preprocessing pipeline. While load_asc() parses EyeLink
.asc files specifically, load_generic() provides a tracker-agnostic
ingestion path: you supply the raw samples (and, optionally, event messages,
gaze coordinates, and blink intervals) as plain R data frames, and
load_generic() assembles them into the same object structure that the rest
of eyeris (including glassbox()) expects.
load_generic( pupil, events = NULL, blinks = NULL, gaze = NULL, sample_rate = NULL, time_unit = c("ms", "s"), block = "auto", eye = c("L", "R", "LR"), pupil_type = c("area", "diameter"), screen_width = NA_real_, screen_height = NA_real_, tracker = "generic", model = NA_character_, mapping = NULL, path = NULL, verbose = TRUE )load_generic( pupil, events = NULL, blinks = NULL, gaze = NULL, sample_rate = NULL, time_unit = c("ms", "s"), block = "auto", eye = c("L", "R", "LR"), pupil_type = c("area", "diameter"), screen_width = NA_real_, screen_height = NA_real_, tracker = "generic", model = NA_character_, mapping = NULL, path = NULL, verbose = TRUE )
pupil |
A data frame of raw samples. Must contain a timestamp column and
a pupil-size column (see |
events |
An optional data frame of event messages with a timestamp column
(on the same clock as |
blinks |
An optional data frame of blink intervals with start and end
timestamp columns. If |
gaze |
An optional data frame of gaze coordinate samples (timestamp,
|
sample_rate |
Numeric sampling rate of the tracker in Hz. If |
time_unit |
Unit of all timestamp columns (in |
block |
Block specification, mirroring
|
eye |
Which eye the data correspond to: |
pupil_type |
Pupil measurement units: |
screen_width, screen_height
|
Optional screen dimensions in pixels, used
for gaze heatmaps and gaze-based confounds. Leave as |
tracker |
Character label for the source tracker/system (default
|
model |
Optional character label for the specific tracker model.
Recorded in |
mapping |
An optional named list remapping the default column names to
the ones present in your data frames. Recognized names are |
path |
Optional character path/identifier stored in the object's |
verbose |
Logical. Whether to print verbose output (default |
eyeris was designed to be extensible, but historically load_asc() was the
only implemented loader. load_generic() closes that gap. Native support for
specific tracker formats (e.g., Tobii, SMI, Pupil Labs, GazePoint) can be
layered on top of this function incrementally: a format-specific reader only
needs to produce the standardized data frames documented below and then call
load_generic().
The resulting object is structurally identical to one returned by
load_asc(), so it is a drop-in input to glassbox() and the
individual preprocessing steps (deblink(), detransient(),
interpolate(), lpfilt(), downsample(),
bin(), detrend(), zscore()),
epoch(), bidsify(), and the plotting methods.
Following the conceptual model that most trackers export, load_generic()
accepts three core data frames (plus an optional fourth for gaze that is
exported separately):
pupil (required) – the raw sample stream. Must contain a timestamp
column and a pupil-size column. May also carry gaze coordinate columns
(eye_x, eye_y) if your tracker exports samples as one wide table.
events (optional) – experimental event messages. Must contain a
timestamp column (on the same clock as pupil) and a message-text column.
Required only if you intend to epoch on event messages later.
blinks (optional) – blink intervals reported by the tracker. Must
contain blink start and end timestamp columns. Note that blink padding via
deblink() does not depend on this table – it reconstructs
missing/blink regions directly from NA (and 0) values in the pupil
column – so this table is purely for record-keeping and export.
A fourth, gaze, data frame is accepted for the less common case where
gaze coordinates are exported separately from pupil size (timestamp + x/y
columns); it is joined onto pupil by timestamp. If your pupil data frame
already contains gaze columns, leave this NULL.
If a column in your data frames does not use the default name expected by
load_generic(), remap it via the mapping argument (see below) rather than
renaming your data by hand.
By default load_generic() looks for these columns (override any of them with
mapping):
| data frame | role | default column |
pupil |
timestamp | time |
pupil |
pupil size | pupil |
pupil |
gaze x (optional) | eye_x |
pupil |
gaze y (optional) | eye_y |
pupil |
block (optional) | block |
events |
timestamp | time |
events |
message text | text |
blinks |
blink start | stime |
blinks |
blink end | etime |
gaze |
timestamp | time |
gaze |
gaze x | eye_x |
gaze |
gaze y | eye_y
|
eyeris assumes EyeLink-style regularly-sampled data. Two practical notes for
other trackers:
Missing samples. deblink() reconstructs missing/blink regions
directly from NA (and 0) values in the pupil column – it does not
require the blinks table. If your tracker drops samples or encodes missing
pupil data some other way, set those samples to NA in the pupil
column so deblinking and the confound calculations behave correctly.
Irregular sampling. If consecutive timestamps are not uniformly spaced,
load_generic() emits a warning, because several downstream steps assume a
fixed sampling interval. (A fuller guardrail is tracked separately.)
An object of S3 class eyeris with the same structure as
load_asc():
file: The path identifier for the source data.
timeseries: A named list of per-block data frames of raw time series
data (time_orig, time_secs, time_scaled, eye_x, eye_y, eye,
hz, type, pupil_raw).
events: A named list of per-block event-message data frames.
blinks: A named list of per-block blink data frames.
info: Tracker metadata (sample.rate, mono, left, right,
pupil.dtype, version, model, screen.x, screen.y).
latest: eyeris pointer for tracking pipeline run history.
binocular, binocular_mode, decimated.sample.rate, params.
load_asc() for loading SR Research EyeLink .asc files.
glassbox() for running the full eyeris preprocessing
pipeline on the object returned by this function.
# build three small standardized data frames from any non-EyeLink tracker set.seed(1) n <- 1000 samples <- data.frame( time = seq(0, by = 1, length.out = n), # 1000 Hz -> 1 ms spacing pupil = 1000 + cumsum(rnorm(n, 0, 5)), eye_x = 960 + rnorm(n, 0, 10), eye_y = 540 + rnorm(n, 0, 10) ) events <- data.frame( time = c(100, 500), text = c("TRIALID 1", "TRIALID 2") ) # construct a valid eyeris object eye <- eyeris::load_generic( pupil = samples, events = events, sample_rate = 1000, screen_width = 1920, screen_height = 1080, tracker = "my-tracker" ) # ...and run it straight through the glassbox pipeline eye |> eyeris::glassbox(lpfilt = list(plot_freqz = FALSE))# build three small standardized data frames from any non-EyeLink tracker set.seed(1) n <- 1000 samples <- data.frame( time = seq(0, by = 1, length.out = n), # 1000 Hz -> 1 ms spacing pupil = 1000 + cumsum(rnorm(n, 0, 5)), eye_x = 960 + rnorm(n, 0, 10), eye_y = 540 + rnorm(n, 0, 10) ) events <- data.frame( time = c(100, 500), text = c("TRIALID 1", "TRIALID 2") ) # construct a valid eyeris object eye <- eyeris::load_generic( pupil = samples, events = events, sample_rate = 1000, screen_width = 1920, screen_height = 1080, tracker = "my-tracker" ) # ...and run it straight through the glassbox pipeline eye |> eyeris::glassbox(lpfilt = list(plot_freqz = FALSE))
The intended use of this method is for smoothing, although by specifying
wp and ws differently one can achieve highpass or bandpass filtering
as well. However, only lowpass filtering should be done on pupillometry data.
lpfilt( eyeris, wp = 4, ws = 8, rp = 1, rs = 35, plot_freqz = FALSE, call_info = NULL )lpfilt( eyeris, wp = 4, ws = 8, rp = 1, rs = 35, plot_freqz = FALSE, call_info = NULL )
eyeris |
An object of class |
wp |
The end of passband frequency in Hz (desired lowpass cutoff).
Defaults to |
ws |
The start of stopband frequency in Hz (required lowpass cutoff).
Defaults to |
rp |
Required maximal ripple within passband in dB. Defaults to |
rs |
Required minimal attenuation within stopband in dB.
Defaults to |
plot_freqz |
A flag to indicate whether to display the filter frequency
response. Defaults to |
call_info |
A list of call information and parameters. If not provided,
it will be generated from the function call. Defaults to |
This function is automatically called by glassbox() by default. If needed,
customize the parameters for lpfilt by providing a parameter list. Use
glassbox(lpfilt = FALSE) to disable this step as needed.
Users should prefer using glassbox() rather than invoking this function
directly unless they have a specific reason to customize the pipeline
manually.
When the input contains gaps longer than the interpolation limit (see
interpolate()'s max_gap_ms) that were left as NA, those gaps
are temporarily filled so the Butterworth filter can run and then masked back
to NA. This can slightly bias the valid samples immediately adjacent to
each gap toward the interpolated values, so a warning is emitted in this
case. If that bias is a concern, consider disabling this step
(glassbox(lpfilt = FALSE)).
An eyeris object with a new column in time series:
pupil_raw_{...}_lpfilt
This function is part of the glassbox() preprocessing pipeline and is not
intended for direct use in most cases. Provide parameters via
lpfilt = list(...).
Advanced users may call it directly if needed.
glassbox() for the recommended way to run this step as
part of the full eyeris glassbox preprocessing pipeline
For a complete, end-to-end reference pipeline that demonstrates how all
eyeris preprocessing functions are chained together in practice, see the
"Building Blocks Under the Hood" section of the Anatomy of an eyeris
Object vignette — vignette("anatomy", package = "eyeris") — as
well as the Complete Pupillometry Pipeline Walkthrough vignette:
vignette("complete-pipeline", package = "eyeris").
demo_data <- eyelink_asc_demo_dataset() demo_data |> # set lpfilt to FALSE (instead of a list of params) to skip step eyeris::glassbox(lpfilt = list(plot_freqz = TRUE)) |> # `preview_window` zooms in on a 2-second subset of the time series plot(seed = 0, preview_window = c(10, 12))demo_data <- eyelink_asc_demo_dataset() demo_data |> # set lpfilt to FALSE (instead of a list of params) to skip step eyeris::glassbox(lpfilt = list(plot_freqz = TRUE)) |> # `preview_window` zooms in on a 2-second subset of the time series plot(seed = 0, preview_window = c(10, 12))
eyeris pipelinepipeline_handler enables flexible integration of custom data
processing functions into the eyeris pipeline. Under the hood,
each preprocessing function in eyeris is a wrapper around a
core operation that gets tracked, versioned, and stored using this
pipeline_handler method. As such, custom pipeline steps must conform
to the eyeris protocol for maximum compatibility with the downstream
functions we provide.
pipeline_handler(eyeris, operation, new_suffix, ...)pipeline_handler(eyeris, operation, new_suffix, ...)
eyeris |
An object of class |
operation |
The name of the function to apply to the time series data.
This custom function should accept a data frame |
new_suffix |
A character string indicating the suffix you would like to be appended to the name of the previous operation's column, which will be used for the new column name in the updated preprocessed data frame(s) |
... |
Additional (optional) arguments passed to the |
Following the eyeris protocol also ensures:
all operations follow a predictable structure, and
that new pupil data columns based on previous operations in the chain are able to be dynamically constructed within the core time series data frame.
An updated eyeris object with the new column added to the
timeseries data frame and the latest pointer updated to the name of the
most recently added column plus all previous columns (ie, the history "trace"
of preprocessing steps from start-to-present)
For more details, please check out the following vignettes:
Anatomy of an eyeris Object
vignette("anatomy", package = "eyeris")
Building Your Own Custom Pipeline Extensions
vignette("custom-extensions", package = "eyeris")
# first, define your custom data preprocessing function winsorize_pupil <- function(x, prev_op, lower = 0.01, upper = 0.99) { vec <- x[[prev_op]] q <- quantile(vec, probs = c(lower, upper), na.rm = TRUE) vec[vec < q[1]] <- q[1] vec[vec > q[2]] <- q[2] vec } # second, construct your `pipeline_handler` method wrapper winsorize <- function(eyeris, lower = 0.01, upper = 0.99, call_info = NULL) { # create call_info if not provided call_info <- if (is.null(call_info)) { list( call_stack = match.call(), parameters = list(lower = lower, upper = upper) ) } else { call_info } # handle binocular objects if (eyeris:::is_binocular_object(eyeris)) { # process left and right eyes independently left_result <- eyeris$left |> pipeline_handler( winsorize_pupil, "winsorize", lower = lower, upper = upper, call_info = call_info ) right_result <- eyeris$right |> pipeline_handler( winsorize_pupil, "winsorize", lower = lower, upper = upper, call_info = call_info ) # return combined structure list_out <- list( left = left_result, right = right_result, original_file = eyeris$original_file, raw_binocular_object = eyeris$raw_binocular_object ) class(list_out) <- "eyeris" return(list_out) } else { # regular eyeris object, process normally eyeris |> pipeline_handler( winsorize_pupil, "winsorize", lower = lower, upper = upper, call_info = call_info ) } } # and voilà, you can now connect your custom extension # directly into your custom `eyeris` pipeline definition! custom_eye <- system.file("extdata", "memory.asc", package = "eyeris") |> eyeris::load_asc(block = "auto") |> eyeris::deblink(extend = 50) |> winsorize() plot(custom_eye, seed = 1)# first, define your custom data preprocessing function winsorize_pupil <- function(x, prev_op, lower = 0.01, upper = 0.99) { vec <- x[[prev_op]] q <- quantile(vec, probs = c(lower, upper), na.rm = TRUE) vec[vec < q[1]] <- q[1] vec[vec > q[2]] <- q[2] vec } # second, construct your `pipeline_handler` method wrapper winsorize <- function(eyeris, lower = 0.01, upper = 0.99, call_info = NULL) { # create call_info if not provided call_info <- if (is.null(call_info)) { list( call_stack = match.call(), parameters = list(lower = lower, upper = upper) ) } else { call_info } # handle binocular objects if (eyeris:::is_binocular_object(eyeris)) { # process left and right eyes independently left_result <- eyeris$left |> pipeline_handler( winsorize_pupil, "winsorize", lower = lower, upper = upper, call_info = call_info ) right_result <- eyeris$right |> pipeline_handler( winsorize_pupil, "winsorize", lower = lower, upper = upper, call_info = call_info ) # return combined structure list_out <- list( left = left_result, right = right_result, original_file = eyeris$original_file, raw_binocular_object = eyeris$raw_binocular_object ) class(list_out) <- "eyeris" return(list_out) } else { # regular eyeris object, process normally eyeris |> pipeline_handler( winsorize_pupil, "winsorize", lower = lower, upper = upper, call_info = call_info ) } } # and voilà, you can now connect your custom extension # directly into your custom `eyeris` pipeline definition! custom_eye <- system.file("extdata", "memory.asc", package = "eyeris") |> eyeris::load_asc(block = "auto") |> eyeris::deblink(extend = 50) |> winsorize() plot(custom_eye, seed = 1)
Creates correlation plots showing the relationship between left and right eye measurements for pupil size, x-coordinates, and y-coordinates. This function is useful for validating binocular data quality and assessing the correlation between the two eyes.
plot_binocular_correlation( eyeris, block = 1, variables = c("pupil", "x", "y"), main = "", col_palette = "viridis", sample_rate = NULL, verbose = TRUE )plot_binocular_correlation( eyeris, block = 1, variables = c("pupil", "x", "y"), main = "", col_palette = "viridis", sample_rate = NULL, verbose = TRUE )
eyeris |
An object of class |
block |
Block number to plot (default: 1) |
variables |
Variables to plot correlations for. Defaults to
|
main |
Title for the overall plot (default: "Binocular Correlation") |
col_palette |
Color palette for the plots (default: "viridis") |
sample_rate |
Sample rate in Hz (optional, for time-based sampling) |
verbose |
Logical flag to indicate whether to print status messages (default: TRUE) |
No return value; creates correlation plots
# For binocular data loaded with binocular_mode = "both" binocular_data <- load_asc(eyelink_asc_binocular_demo_dataset(), binocular_mode = "both") plot_binocular_correlation(binocular_data) # For binocular data loaded with binocular_mode = "average" # (correlation plot will show original left vs right before averaging) avg_data <- load_asc(eyelink_asc_binocular_demo_dataset(), binocular_mode = "average") plot_binocular_correlation(avg_data$raw_binocular_object)# For binocular data loaded with binocular_mode = "both" binocular_data <- load_asc(eyelink_asc_binocular_demo_dataset(), binocular_mode = "both") plot_binocular_correlation(binocular_data) # For binocular data loaded with binocular_mode = "average" # (correlation plot will show original left vs right before averaging) avg_data <- load_asc(eyelink_asc_binocular_demo_dataset(), binocular_mode = "average") plot_binocular_correlation(avg_data$raw_binocular_object)
Creates a heatmap showing the distribution of eye_x and eye_y coordinates across the entire screen area. The heatmap shows where the participant looked most frequently during the recording period.
plot_gaze_heatmap( eyeris, block = 1, screen_width = NULL, screen_height = NULL, n_bins = 50, col_palette = "viridis", main = "Gaze Heatmap", xlab = "Screen X (pixels)", ylab = "Screen Y (pixels)", sample_rate = NULL, eye_suffix = NULL )plot_gaze_heatmap( eyeris, block = 1, screen_width = NULL, screen_height = NULL, n_bins = 50, col_palette = "viridis", main = "Gaze Heatmap", xlab = "Screen X (pixels)", ylab = "Screen Y (pixels)", sample_rate = NULL, eye_suffix = NULL )
eyeris |
An object of class |
block |
Block number to plot (default: 1) |
screen_width |
Screen width in pixels from |
screen_height |
Screen height in pixels from |
n_bins |
Number of bins for the heatmap grid (default: 50) |
col_palette |
Color palette for the heatmap (default: "viridis") |
main |
Title for the plot (default: "Fixation Heatmap") |
xlab |
X-axis label (default: "Screen X (pixels)") |
ylab |
Y-axis label (default: "Screen Y (pixels)") |
sample_rate |
Sample rate in Hz (optional) |
eye_suffix |
Eye suffix for binocular data (default: NULL) |
No return value; creates a heatmap plot
demo_data <- eyelink_asc_demo_dataset() eyeris_preproc <- glassbox(demo_data) plot_gaze_heatmap(eyeris = eyeris_preproc, block = 1)demo_data <- eyelink_asc_demo_dataset() eyeris_preproc <- glassbox(demo_data) plot_gaze_heatmap(eyeris = eyeris_preproc, block = 1)
eyeris
S3 plotting method for objects of class eyeris. Plots a single-panel
timeseries for a subset of the pupil time series at each preprocessing step.
The intended use of this function is to provide a simple method for
qualitatively assessing the consequences of the preprocessing recipe and
parameters on the raw pupillary signal.
## S3 method for class 'eyeris' plot( x, ..., steps = NULL, preview_n = NULL, preview_duration = NULL, preview_window = NULL, seed = NULL, block = 1, plot_distributions = FALSE, suppress_prompt = TRUE, verbose = TRUE, add_progressive_summary = FALSE, eye = c("left", "right", "both"), num_previews = deprecated() )## S3 method for class 'eyeris' plot( x, ..., steps = NULL, preview_n = NULL, preview_duration = NULL, preview_window = NULL, seed = NULL, block = 1, plot_distributions = FALSE, suppress_prompt = TRUE, verbose = TRUE, add_progressive_summary = FALSE, eye = c("left", "right", "both"), num_previews = deprecated() )
x |
An object of class |
... |
Additional arguments to be passed to |
steps |
Which steps to plot; defaults to |
preview_n |
Number of random example "epochs" to generate for previewing the effect of each preprocessing step on the pupil time series |
preview_duration |
Time in seconds of each randomly selected preview |
preview_window |
The start and stop raw timestamps used to subset the
preprocessed data from each step of the |
seed |
Random seed for current plotting session. Leave NULL to select
|
block |
For multi-block recordings, specifies which block to plot.
Defaults to 1. When a single |
plot_distributions |
Logical flag to indicate whether to plot both
diagnostic pupil time series and accompanying histograms of the pupil
samples at each processing step. Defaults to |
suppress_prompt |
Logical flag to disable interactive confirmation
prompts during plotting. Defaults to |
verbose |
A logical flag to indicate whether to print status messages to
the console. Defaults to |
add_progressive_summary |
Logical flag to indicate whether to add a
progressive summary plot after plotting. Defaults to |
eye |
For binocular data, specifies which eye to plot: "left", "right", or "both". Defaults to "left". For "both", currently plots left eye data (use eye="right" for right eye data) |
num_previews |
(Deprecated) Use |
No return value; iteratively plots a subset of the pupil time series from each preprocessing step run
# first, generate the preprocessed pupil data my_eyeris_data <- system.file("extdata", "memory.asc", package = "eyeris") |> eyeris::load_asc() |> eyeris::deblink(extend = 50) |> eyeris::detransient() |> eyeris::interpolate() |> eyeris::lpfilt(plot_freqz = TRUE) |> eyeris::zscore() # controlling the time series range (i.e., preview window) in your plots: # (note: omitting `preview_window` uses the default 10000 to 20000 ms # subset; the examples below pass shorter windows explicitly so that they # run quickly) ## example 1: a 2-second time subset (i.e., 10000 to 12000 ms) plot( my_eyeris_data, preview_window = c(10, 12), seed = 0, add_progressive_summary = TRUE ) ## example 2: using a custom time subset (i.e., 1 to 500 ms) plot( my_eyeris_data, preview_window = c(0.01, 0.5), seed = 0 ) # controlling which block of data you would like to plot: # (`block` defaults to 1, i.e., the first block; here it is combined with a # custom preview window of 1000 to 2000 ms) plot( my_eyeris_data, block = 1, preview_window = c(1, 2), seed = 0 )# first, generate the preprocessed pupil data my_eyeris_data <- system.file("extdata", "memory.asc", package = "eyeris") |> eyeris::load_asc() |> eyeris::deblink(extend = 50) |> eyeris::detransient() |> eyeris::interpolate() |> eyeris::lpfilt(plot_freqz = TRUE) |> eyeris::zscore() # controlling the time series range (i.e., preview window) in your plots: # (note: omitting `preview_window` uses the default 10000 to 20000 ms # subset; the examples below pass shorter windows explicitly so that they # run quickly) ## example 1: a 2-second time subset (i.e., 10000 to 12000 ms) plot( my_eyeris_data, preview_window = c(10, 12), seed = 0, add_progressive_summary = TRUE ) ## example 2: using a custom time subset (i.e., 1 to 500 ms) plot( my_eyeris_data, preview_window = c(0.01, 0.5), seed = 0 ) # controlling which block of data you would like to plot: # (`block` defaults to 1, i.e., the first block; here it is combined with a # custom preview window of 1000 to 2000 ms) plot( my_eyeris_data, block = 1, preview_window = c(1, 2), seed = 0 )
Print a synthetic pupil parameter list
## S3 method for class 'eyeris_sim_params' print(x, ...)## S3 method for class 'eyeris_sim_params' print(x, ...)
x |
An object of class |
... |
Unused; included for S3 consistency |
x, invisibly
Handles really large databases by processing queries in reasonably sized chunks to avoid memory issues. Data can be written to CSV or Parquet files as it's processed.
process_chunked_query( con, query, chunk_size = 1e+06, output_file = NULL, process_chunk = NULL, verbose = TRUE )process_chunked_query( con, query, chunk_size = 1e+06, output_file = NULL, process_chunk = NULL, verbose = TRUE )
con |
Database connection |
query |
SQL query string to execute |
chunk_size |
Number of rows to fetch per chunk (default: 1000000) |
output_file |
Optional output file path for writing chunks. If provided, chunks will be appended to this file. File format determined by extension (.csv or .parquet) |
process_chunk |
Optional function to process each chunk. Function should accept a data.frame and return logical indicating success. If not provided and output_file is specified, chunks are written to file. |
verbose |
Whether to print progress messages (default: TRUE) |
List containing summary information about the chunked processing
## Not run: # These examples require an existing eyeris database con <- eyeris_db_connect("/path/to/bids", "my-project") # Process large query and write to CSV process_chunked_query( con, "SELECT * FROM large_table WHERE condition = 'something'", chunk_size = 50000, output_file = "large_export.csv" ) # Process large query with custom chunk processing process_chunked_query( con, "SELECT * FROM large_table", chunk_size = 25000, process_chunk = function(chunk) { # Custom processing here processed_data <- some_analysis(chunk) return(TRUE) } ) eyeris_db_disconnect(con) ## End(Not run)## Not run: # These examples require an existing eyeris database con <- eyeris_db_connect("/path/to/bids", "my-project") # Process large query and write to CSV process_chunked_query( con, "SELECT * FROM large_table WHERE condition = 'something'", chunk_size = 50000, output_file = "large_export.csv" ) # Process large query with custom chunk processing process_chunked_query( con, "SELECT * FROM large_table", chunk_size = 25000, process_chunk = function(chunk) { # Custom processing here processed_data <- some_analysis(chunk) return(TRUE) } ) eyeris_db_disconnect(con) ## End(Not run)
Convenience function to read the parquet files created by eyeris_db_to_parquet back into a single data frame or list of data frames by data type.
read_eyeris_parquet( parquet_dir, db_name = NULL, data_type = NULL, return_list = FALSE, pattern = "*.parquet", verbose = TRUE )read_eyeris_parquet( parquet_dir, db_name = NULL, data_type = NULL, return_list = FALSE, pattern = "*.parquet", verbose = TRUE )
parquet_dir |
Directory containing the parquet files, or path to database-specific folder |
db_name |
Optional database name to read from (if parquet_dir contains multiple database folders) |
data_type |
Optional data type to read (if NULL, reads all data types) |
return_list |
Whether to return a list by data type (TRUE) or combined data frame (FALSE, default) |
pattern |
Pattern to match parquet files (default: "*.parquet") |
verbose |
Whether to print progress messages (default: TRUE) |
Combined data frame from all parquet files, or list of data frames by data type
# Minimal self-contained example that avoids database creation if (requireNamespace("arrow", quietly = TRUE)) { # create a temporary folder structure: parquet/<db_name> base_dir <- file.path(tempdir(), "derivatives", "parquet") db_name <- "example-db" dir.create(file.path(base_dir, db_name), recursive = TRUE, showWarnings = FALSE) # write two small parquet parts for a single data type part1 <- data.frame(time = 1:5, value = 1:5) part2 <- data.frame(time = 6:10, value = 6:10) arrow::write_parquet( part1, file.path( base_dir, db_name, paste0(db_name, "_timeseries_part-01-of-02.parquet") ) ) arrow::write_parquet( part2, file.path( base_dir, db_name, paste0(db_name, "_timeseries_part-02-of-02.parquet") ) ) # read them back as combined data frame data <- read_eyeris_parquet(base_dir, db_name = db_name) # read as list by data type data_by_type <- read_eyeris_parquet(base_dir, db_name = db_name, return_list = TRUE) # read specific data type only timeseries_data <- read_eyeris_parquet(base_dir, db_name = db_name, data_type = "timeseries") }# Minimal self-contained example that avoids database creation if (requireNamespace("arrow", quietly = TRUE)) { # create a temporary folder structure: parquet/<db_name> base_dir <- file.path(tempdir(), "derivatives", "parquet") db_name <- "example-db" dir.create(file.path(base_dir, db_name), recursive = TRUE, showWarnings = FALSE) # write two small parquet parts for a single data type part1 <- data.frame(time = 1:5, value = 1:5) part2 <- data.frame(time = 6:10, value = 6:10) arrow::write_parquet( part1, file.path( base_dir, db_name, paste0(db_name, "_timeseries_part-01-of-02.parquet") ) ) arrow::write_parquet( part2, file.path( base_dir, db_name, paste0(db_name, "_timeseries_part-02-of-02.parquet") ) ) # read them back as combined data frame data <- read_eyeris_parquet(base_dir, db_name = db_name) # read as list by data type data_by_type <- read_eyeris_parquet(base_dir, db_name = db_name, return_list = TRUE) # read specific data type only timeseries_data <- read_eyeris_parquet(base_dir, db_name = db_name, data_type = "timeseries") }
Places each recording segment onto the expected uniform (consistently-spaced)
sampling grid: local sub-period timing jitter is smoothed by linear
interpolation, and longer-than-expected gaps (dropped samples) become explicit
NA rows for the later interpolate() step to handle.
resample(eyeris, verbose = TRUE, call_info = NULL)resample(eyeris, verbose = TRUE, call_info = NULL)
eyeris |
An object of class |
verbose |
A flag to indicate whether to print detailed logging messages.
Defaults to |
call_info |
A list of call information and parameters. If not provided, it will be generated from the function call |
Most of the eyeris pipeline (e.g., detransient(),
lpfilt(), downsample()) assumes a fixed sampling rate.
EyeLink trackers honor that assumption by zero-filling missing pupil samples,
but some hardware instead drops samples entirely when pupil data is
missing, leaving holes in the otherwise evenly-spaced time vector. Those
holes silently distort any rate-dependent step.
resample() repairs the time axis in two stages. Whether a block needs
repair is decided by the robust check_uniform_sampling_intervals() detector,
which distinguishes genuine dropped samples from data that only looks
irregular – notably high-rate trackers that report integer-millisecond
timestamps for sub-millisecond samples (these are left untouched, so genuine
samples are never collapsed). For blocks it does repair:
Build the target grid. The uniform grid is anchored on the first reliable regular interval – the first observed interval that matches the expected sampling period – rather than on the first timestamp. This keeps early sub-period jitter (e.g., intervals of 3, 3, 4, 4 ms at a 4 ms period) from offsetting the whole grid. The grid is then extended in both directions at the expected period so that it spans every observed timestamp, including any samples that precede the anchor (back-extension).
Resample onto the grid. Observed samples are placed on the grid by
linear interpolation: samples that land on a grid point are kept verbatim,
and short/jittered intervals are interpolated across so their values
contribute to the regular grid. Any observed interval longer than the
expected period is treated as a real gap: the missing grid sample(s) inside
it are inserted as NA rather than interpolated across, and flagged in a
new logical is_resampled column so they can be tracked downstream.
resample() does not fill the inserted NA values itself; that is the
job of interpolate(), which decides how much of a missing span to
fill according to its own missing-data policy. Running resample() therefore
turns the "dropped-sample" problem into the ordinary "missing-value" (NA)
problem that the rest of the pipeline already handles.
For data that is already uniformly sampled (e.g., EyeLink), resample() is a
guaranteed no-op: no rows are inserted, no is_resampled column is added, and
the data is returned unchanged. The same is true of a block whose surviving
samples already form a uniform (coarser) grid – e.g., pure systematic
decimation – where there is nothing to insert without fabricating data.
An eyeris object whose timeseries blocks have been placed on a
uniform time grid, with a new logical is_resampled column marking inserted
(gap) rows.
This step is part of the glassbox() preprocessing pipeline and runs
automatically by default (it is a no-op unless irregular sampling is
detected). Opt out with glassbox(resample = FALSE). Advanced users may call
it directly if needed.
interpolate() for filling the gaps left by dropped
samples, and glassbox() for the recommended way to run this step
as part of the full eyeris glassbox preprocessing pipeline.
Builds the parameter list that controls simulate_eyeris(). Every
component of the synthetic pupil time series (tonic baseline, slow drift,
hippus, task-evoked phasic responses, blinks, transient spikes, measurement
noise, and optional line noise) is independently toggle-able and
parameterized here, so a single scenario can isolate exactly the feature it
needs to teach.
sim_params( fs = 1000L, duration_secs = 60, baseline = TRUE, baseline_mean = 5000, drift = FALSE, drift_slope = 8, drift_curv = 0, hippus = TRUE, hippus_amp = 40, hippus_freq = 0.12, phasic = TRUE, phasic_n = 10.1, phasic_tmax = 0.93, phasic_amp = c(120, 300), phasic_isi = c(6, 8), phasic_onsets_ms = NULL, blinks = TRUE, n_blinks = 6, blink_dur_ms = c(100, 300), blink_flank_ms = 40, blink_depth = 400, blink_overshoot = 300, blink_flank_shape = c("linear", "cosine"), transients = TRUE, n_transients = 3, transient_amp = c(200, 350), transient_width_ms = 1.5, noise = TRUE, noise_sd = 3, noise_ar = 0, line = FALSE, line_freq = 96, line_amp = 20, dropout_frac = 0.003, dropout_run_ms = c(1, 5), clip = c(3200, 7200), eye = c("R", "L"), pupil_dtype = c("DIAMETER", "AREA") )sim_params( fs = 1000L, duration_secs = 60, baseline = TRUE, baseline_mean = 5000, drift = FALSE, drift_slope = 8, drift_curv = 0, hippus = TRUE, hippus_amp = 40, hippus_freq = 0.12, phasic = TRUE, phasic_n = 10.1, phasic_tmax = 0.93, phasic_amp = c(120, 300), phasic_isi = c(6, 8), phasic_onsets_ms = NULL, blinks = TRUE, n_blinks = 6, blink_dur_ms = c(100, 300), blink_flank_ms = 40, blink_depth = 400, blink_overshoot = 300, blink_flank_shape = c("linear", "cosine"), transients = TRUE, n_transients = 3, transient_amp = c(200, 350), transient_width_ms = 1.5, noise = TRUE, noise_sd = 3, noise_ar = 0, line = FALSE, line_freq = 96, line_amp = 20, dropout_frac = 0.003, dropout_run_ms = c(1, 5), clip = c(3200, 7200), eye = c("R", "L"), pupil_dtype = c("DIAMETER", "AREA") )
fs |
Sampling rate in Hz. Must divide 1000 evenly (e.g. |
duration_secs |
Recording duration in seconds. Defaults to |
baseline |
Logical; include the tonic baseline offset. Defaults to |
baseline_mean |
Tonic baseline pupil size in a.u. Defaults to |
drift |
Logical; include a slow linear drift. Defaults to |
drift_slope |
Drift slope magnitude in a.u. per second (applied as a
decline). Defaults to |
drift_curv |
Quadratic drift coefficient in a.u. per second squared.
Defaults to |
hippus |
Logical; include the slow hippus oscillation. Defaults to |
hippus_amp |
Hippus amplitude in a.u. Defaults to |
hippus_freq |
Hippus frequency in Hz. Defaults to |
phasic |
Logical; include task-evoked phasic responses. Defaults to
|
phasic_n |
Erlang shape parameter (unitless). Defaults to |
phasic_tmax |
Time-to-peak of the phasic response in seconds. Defaults
to |
phasic_amp |
Length-2 numeric |
phasic_isi |
Length-2 numeric |
phasic_onsets_ms |
Optional numeric vector of explicit stimulus onset
times in milliseconds. When |
blinks |
Logical; include blink artifacts. Defaults to |
n_blinks |
Number of blinks. Defaults to |
blink_dur_ms |
Length-2 numeric |
blink_flank_ms |
Duration in milliseconds of the occlusion spike on each
side of a blink core. Keep below the |
blink_depth |
Depth in a.u. of the leading occlusion dip. Defaults to
|
blink_overshoot |
Height in a.u. of the trailing recovery overshoot.
Defaults to |
blink_flank_shape |
Shape of the occlusion ramp: |
transients |
Logical; include isolated transient spikes. Defaults to
|
n_transients |
Number of transient spikes. Defaults to |
transient_amp |
Length-2 numeric |
transient_width_ms |
Gaussian width (standard deviation) of each
transient in milliseconds. Defaults to |
noise |
Logical; include broadband measurement noise. Should remain
|
noise_sd |
Standard deviation of the white measurement noise in a.u.
Defaults to |
noise_ar |
Lag-1 autocorrelation for optional AR(1) noise coloring in
|
line |
Logical; include a high-frequency line-noise tone. Defaults to
|
line_freq |
Line-noise frequency in Hz. Defaults to |
line_amp |
Line-noise amplitude in a.u. Defaults to |
dropout_frac |
Fraction of (non-blink) samples to mark missing as short
scattered dropout runs. Defaults to |
dropout_run_ms |
Length-2 numeric |
clip |
Length-2 numeric |
eye |
Which eye to label the data as ( |
pupil_dtype |
Pupil data type label ( |
The synthetic signal is composed additively in arbitrary units (a.u.) matched to real EyeLink pupil diameter data (which typically ranges from roughly 3600–7000 a.u.):
after which blink and dropout artifacts are stamped on (see
simulate_eyeris()).
Components (each independently toggle-able):
Tonic baseline (baseline): a constant offset baseline_mean.
Linear drift (drift): a slow tonic decline
(a.u. per second), plus an optional
quadratic term drift_curv. This is precisely what
detrend() removes.
Hippus (hippus): a slow arousal oscillation
that sits below the low-pass cutoff and
therefore survives lpfilt().
Phasic response (phasic): the canonical task-evoked pupil
response modeled with the Hoeks & Levelt (1993) Erlang gamma kernel
, peak-normalized and scaled by
phasic_amp, time-locked to stimulus onsets.
Blinks (blinks): runs of missing data (NA) flanked by the
rapid partial-occlusion down-then-up spikes that surround real blinks —
the artifact deblink() is designed to remove.
Transients (transients): isolated, physiologically implausible
fast spikes (tracker glitches) that detransient() targets.
Noise (noise): broadband white measurement noise. Always keep
noise_sd > 0 — a perfectly noiseless signal makes the median absolute
deviation of the pupil speed zero, which aborts detransient().
Line noise (line): an optional high-frequency tone used to
demonstrate aliasing when decimating without an anti-alias filter.
A named list of class eyeris_sim_params.
simulate_eyeris() to generate data from these parameters.
# default parameters p <- sim_params() # a short recording with a strong linear drift for a detrend demo p2 <- sim_params(duration_secs = 20, drift = TRUE, drift_slope = 12)# default parameters p <- sim_params() # a short recording with a strong linear drift for a detrend demo p2 <- sim_params(duration_secs = 20, drift = TRUE, drift_slope = 12)
eyeris object with realistic pupil characteristicsSimulates a pupil time series whose statistical and morphological
characteristics resemble real EyeLink recordings — a tonic baseline with
slow drift and hippus, canonical task-evoked phasic dilations, blinks with
partial-occlusion flank spikes, isolated transient artifacts, measurement
noise, and optional high-frequency line noise — and wraps it in a fully
valid S3 eyeris object.
Because the returned object is byte-compatible with the output of
load_asc(), it flows unchanged through the entire eyeris
pipeline: deblink(), detransient(),
interpolate(), lpfilt(), downsample(),
detrend(), zscore(), plot.eyeris(),
epoch(), and summarize_confounds().
simulate_eyeris(seed = 1L, params = sim_params(), block = 1L, verbose = TRUE)simulate_eyeris(seed = 1L, params = sim_params(), block = 1L, verbose = TRUE)
seed |
Integer random seed for reproducible generation. Defaults to |
params |
A parameter list from |
block |
Numeric block label written into the time series. Defaults to
|
verbose |
Logical; print a short status message. Defaults to |
The signal is generated deterministically given seed: the same seed and
params always yield an identical object, and the global random number
generator state is left untouched (generation is confined via
withr::with_seed()). See sim_params() for the full generative
model and every tunable component.
The clean latent signal (before artifacts and noise) and the indices of the
injected artifacts are attached to the returned object as the attribute
"sim_truth" for use as ground truth in quantitative demonstrations; note
that this attribute does not survive the pipeline step functions (which
rebuild the time series data frame), so downstream tooling recomputes ground
truth from a retained copy rather than relying on the attribute.
An object of S3 class eyeris (see the Anatomy of an eyeris
Object vignette — vignette("anatomy", package = "eyeris")), with
a "sim_truth" attribute describing the ground-truth signal.
sim_params() to configure the synthetic signal.
# generate a synthetic recording and run it through the pipeline sim <- simulate_eyeris(seed = 1, params = sim_params(duration_secs = 20)) out <- sim |> eyeris::deblink() |> eyeris::detransient() |> eyeris::interpolate() |> eyeris::lpfilt() |> eyeris::zscore() pdf(tempfile(fileext = ".pdf")) plot(out, seed = 1) dev.off()# generate a synthetic recording and run it through the pipeline sim <- simulate_eyeris(seed = 1, params = sim_params(duration_secs = 20)) out <- sim |> eyeris::deblink() |> eyeris::detransient() |> eyeris::interpolate() |> eyeris::lpfilt() |> eyeris::zscore() pdf(tempfile(fileext = ".pdf")) plot(out, seed = 1) dev.off()
Calculates various confounding variables for pupil data, including blink
statistics, gaze position metrics, and pupil size characteristics. These
confounds are calculated separately for each preprocessing step, recording
block, and epoched time series in the eyeris object.
summarize_confounds(eyeris)summarize_confounds(eyeris)
eyeris |
An object of class |
An eyeris object with a new nested list of data frames:
$confounds
The confounds are organized hierarchically by block and preprocessing step.
Each step contains metrics such as:
Missing data (n_missing and prop_missing)
Blink rate and duration statistics
Gaze position (x,y) mean and standard deviation
Pupil size mean, standard deviation, and range
Missing data is reported as both a count (n_missing) and a proportion
(prop_missing, 0 to 1; multiply by 100 for a percentage) of NA
samples. It is computed at two levels so you can choose the granularity
appropriate to your design:
Block level: confounds$unepoched_timeseries[[block]][[step]]
reports prop_missing across the entire recording block.
Epoch/trial level: confounds$epoched_timeseries[[epoch]][[block]]
reports prop_missing for each epoched event (e.g., per trial).
eyeris intentionally does not enforce a fixed missing-data exclusion
cutoff. Instead, prop_missing is exposed so you can define your own
exclusion thresholds at whichever level (trial, epoch, or block) is
appropriate for your study.
# load demo dataset demo_data <- eyelink_asc_demo_dataset() # calculate confounds for all blocks and preprocessing steps confounds <- demo_data |> eyeris::glassbox() |> eyeris::epoch( events = "PROBE_{type}_{trial}", limits = c(-1, 1), # grab 1 second prior to and 1 second post event label = "prePostProbe" # custom epoch label name ) |> eyeris::summarize_confounds() # access confounds for entire time series for a specific block and step confounds$confounds$unepoched_timeseries # access confounds for a specific epoched time series # for a specific block and step confounds$confounds$epoched_timeseries confounds$confounds$epoched_epoch_wide# load demo dataset demo_data <- eyelink_asc_demo_dataset() # calculate confounds for all blocks and preprocessing steps confounds <- demo_data |> eyeris::glassbox() |> eyeris::epoch( events = "PROBE_{type}_{trial}", limits = c(-1, 1), # grab 1 second prior to and 1 second post event label = "prePostProbe" # custom epoch label name ) |> eyeris::summarize_confounds() # access confounds for entire time series for a specific block and step confounds$confounds$unepoched_timeseries # access confounds for a specific epoched time series # for a specific block and step confounds$confounds$epoched_timeseries confounds$confounds$epoched_epoch_wide
The intended use of this method is to scale the arbitrary units of the pupil
size time series to have a mean of 0 and a standard deviation of 1. This
is accomplished by mean centering the data points and then dividing them by
their standard deviation (i.e., z-scoring the data, similar to
base::scale()). Opting to z-score your pupil data helps with trial-level
and between-subjects analyses where arbitrary units of pupil size recorded by
the tracker do not scale across participants, and therefore make analyses
that depend on data from more than one participant difficult to interpret.
zscore(eyeris, call_info = NULL)zscore(eyeris, call_info = NULL)
eyeris |
An object of class |
call_info |
A list of call information and parameters. If not provided, it will be generated from the function call |
This function is automatically called by glassbox() by default. Use
glassbox(zscore = FALSE) to disable this step as needed.
Users should prefer using glassbox() rather than invoking this function
directly unless they have a specific reason to customize the pipeline
manually.
In general, it is common to z-score pupil data within any given participant, and furthermore, z-score that participant's data as a function of block number (for tasks/experiments where participants complete more than one block of trials) to account for potential time-on-task effects across task/experiment blocks.
As such, if you use the eyeris package as intended, you should NOT need
to specify any groups for the participant/block-level situations described
above. This is because eyeris is designed to preprocess a single block of
pupil data for a single participant, one at a time. Therefore, when you later
merge all of the preprocessed data from eyeris, each individual,
preprocessed block of data for each participant will have already been
independently scaled from the others.
Additionally, if you intend to compare mean z-scored pupil size across task conditions, such as that for memory successes vs. memory failures, then do NOT set your behavioral outcome (i.e., success/failure) variable as a grouping variable within your analysis. If you do, you will consequently obtain a mean pupil size of 0 and standard deviation of 1 within each group (since the scaled pupil size would be calculated on the time series from each outcome variable group, separately). Instead, you should compute the z-score on the entire pupil time series (before epoching the data), and then split and take the mean of the z-scored time series as a function of condition variable.
An eyeris object with a new column in time series:
pupil_raw_{...}_z
This function is part of the glassbox() preprocessing pipeline and is not
intended for direct use in most cases. Use glassbox(zscore = TRUE).
Advanced users may call it directly if needed.
glassbox() for the recommended way to run this step as
part of the full eyeris glassbox preprocessing pipeline
For a complete, end-to-end reference pipeline that demonstrates how all
eyeris preprocessing functions are chained together in practice, see the
"Building Blocks Under the Hood" section of the Anatomy of an eyeris
Object vignette — vignette("anatomy", package = "eyeris") — as
well as the Complete Pupillometry Pipeline Walkthrough vignette:
vignette("complete-pipeline", package = "eyeris").
demo_data <- eyelink_asc_demo_dataset() demo_data |> eyeris::glassbox(zscore = TRUE) |> # set to FALSE to skip (not recommended) # `preview_window` zooms in on a 2-second subset of the time series plot(seed = 0, preview_window = c(10, 12))demo_data <- eyelink_asc_demo_dataset() demo_data |> eyeris::glassbox(zscore = TRUE) |> # set to FALSE to skip (not recommended) # `preview_window` zooms in on a 2-second subset of the time series plot(seed = 0, preview_window = c(10, 12))