-
-
Notifications
You must be signed in to change notification settings - Fork 5
epoching bids reports
eyeris was intentionally designed for intuitive, flexible
preprocessing of pupillometry data, with support for event-based
epoching and BIDS-style organization for reproducible workflows.
In this vignette, weβll walk through a typical use case:
- loading raw data,
- preprocessing it,
- extracting trial-based epochs,
- and exporting everything in a clean, analysis-ready format.
Weβll also demonstrate a unique feature we designed to maximize both
your productivity as well as data quality: interactive HTML reports,
which include a record of the steps used to preprocess / epoch any given
dataset β and, for epoched data β an interactive βgalleryβ view to
quickly skim through trial-level data from each step of the
preprocessing pipeline to make quality control and assurance intuitive
and accessible for any dataset (without needing to write any additional
code)!
# Load eyeris
library(eyeris)
# Load the example memory task file and run default glassbox preproc workflow
demo_data <- eyelink_asc_demo_dataset()
eye <- glassbox(demo_data)
#> β [2026-07-19 05:45:09] [OKAY] Running eyeris::load_asc()
#> βΉ [2026-07-19 05:45:09] [INFO] Processing block: block_1
#> β [2026-07-19 05:45:09] [OKAY] Running eyeris::deblink() for block_1
#> β [2026-07-19 05:45:09] [OKAY] Running eyeris::detransient() for block_1
#> β [2026-07-19 05:45:09] [OKAY] Running eyeris::interpolate() for block_1
#> β [2026-07-19 05:45:09] [OKAY] Running eyeris::lpfilt() for block_1
#> ! [2026-07-19 05:45:09] [WARN] Skipping eyeris::downsample() for block_1
#> ! [2026-07-19 05:45:09] [WARN] Skipping eyeris::bin() for block_1
#> ! [2026-07-19 05:45:09] [WARN] Skipping eyeris::detrend() for block_1
#> β [2026-07-19 05:45:09] [OKAY] Running eyeris::zscore() for block_1
#> βΉ [2026-07-19 05:45:09] [INFO] Block processing summary:
#> βΉ [2026-07-19 05:45:09] [INFO] block_1: OK (steps: 6, latest:
#> pupil_raw_deblink_detransient_interpolate_lpfilt_z)
#> β [2026-07-19 05:45:09] [OKAY] Running eyeris::summarize_confounds()epoch() enables flexible extraction of trials using:
- start/stop events,
- string-based patterns,
- and even embedded trial metadata.
Extract a 2-second window centered around each βPROBEβ event.
eye_1a <- eye |>
epoch(events = "PROBE*", limits = c(-1, 1))
#> βΉ [2026-07-19 05:45:09] [INFO] Epoching pupil data...
#> βΉ [2026-07-19 05:45:09] [INFO] Block 1: found 10 matching events for PROBE
#> β [2026-07-19 05:45:10] [OKAY] Done!
#> β [2026-07-19 05:45:10] [OKAY] Block 1: pupil data from 10 unique event
#> messages extracted
#> β [2026-07-19 05:45:10] [OKAY] Pupil epoching completed in 0.20 seconds
#> βΉ [2026-07-19 05:45:10] [INFO] Recalculating epoched confounds for new
#> epochs...Now, if you take a look at eye, youβll notice thereβs a new list
element within this eyeris object: epoch_probe.
eye_1a$epoch_probe
#> $block_1
#> # A tibble: 20,000 Γ 21
#> block time_orig timebin time_secs time_scaled eye_x eye_y eye hz type
#> <dbl> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <chr> <dbl> <chr>
#> 1 1 11335474 0 0.983 0.983 975. 545. R 1000 diametβ¦
#> 2 1 11335475 0.00100 0.984 0.984 975. 544. R 1000 diametβ¦
#> 3 1 11335476 0.00200 0.985 0.985 975. 544 R 1000 diametβ¦
#> 4 1 11335477 0.00300 0.986 0.986 976. 544. R 1000 diametβ¦
#> 5 1 11335478 0.00400 0.987 0.987 975. 545 R 1000 diametβ¦
#> 6 1 11335479 0.00500 0.988 0.988 975. 545. R 1000 diametβ¦
#> 7 1 11335480 0.00600 0.989 0.989 975. 544. R 1000 diametβ¦
#> 8 1 11335481 0.00700 0.99 0.99 975. 544. R 1000 diametβ¦
#> 9 1 11335482 0.00800 0.991 0.991 975 545. R 1000 diametβ¦
#> 10 1 11335483 0.00900 0.992 0.992 975. 546. R 1000 diametβ¦
#> # βΉ 19,990 more rows
#> # βΉ 11 more variables: pupil_raw <dbl>, pupil_raw_deblink <dbl>,
#> # pupil_raw_deblink_detransient <dbl>,
#> # pupil_raw_deblink_detransient_interpolate <dbl>,
#> # pupil_raw_deblink_detransient_interpolate_lpfilt <dbl>,
#> # pupil_raw_deblink_detransient_interpolate_lpfilt_z <dbl>,
#> # text_unique <chr>, template <chr>, matching_pattern <chr>, β¦
#>
#> $info
#> $info$block_1
#> $info$block_1$calc_baseline
#> [1] FALSE
#>
#> $info$block_1$apply_baseline
#> [1] FALSE
#>
#> $info$block_1$epoch_events
#> [1] "PROBE*"
#>
#> $info$block_1$epoch_limits
#> [1] -1 1
#>
#> $info$block_1$n_epochs
#> [1] 10By default, the resulting eyeris object will contain the epoched data
frame within a list element called epoch_xyz where xyz will be a
sanitized version of the original start event string you supplied for
the pattern matching procedure.
However, you have the ability to customize this label, by passing a
value to the label argument within epoch().
eyeris object (e.g., epoch_, or
perhaps even $epoch_nana, etc.). The extracted data epochs should
still be accessible here, however, to avoid ambiguous list objects, we
highly recommend you explicitly supply sensible epoch labels here
within your epoch() calls to be safe.
Extract the 1-second window after βPROBE_STARTβ and apply a custom label to the resulting epoch set.
eye_1b <- eye |>
epoch(
events = "PROBE_START_{trial}",
limits = c(0, 1),
label = "probeAfter"
)
#> βΉ [2026-07-19 05:45:10] [INFO] Epoching pupil data...
#> βΉ [2026-07-19 05:45:10] [INFO] Block 1: found 5 matching events for
#> PROBESTARTtrial
#> β [2026-07-19 05:45:10] [OKAY] Done!
#> β [2026-07-19 05:45:10] [OKAY] Block 1: pupil data from 5 unique event messages
#> extracted
#> β [2026-07-19 05:45:10] [OKAY] Pupil epoching completed in 0.07 seconds
#> βΉ [2026-07-19 05:45:10] [INFO] Recalculating epoched confounds for new
#> epochs...
eye_1b |>
purrr::pluck("epoch_probeAfter") |>
head()
#> $block_1
#> # A tibble: 5,000 Γ 21
#> block time_orig timebin time_secs time_scaled eye_x eye_y eye hz type
#> <dbl> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <chr> <dbl> <chr>
#> 1 1 11336474 0 1.98 1.98 972. 550. R 1000 diametβ¦
#> 2 1 11336475 0.00100 1.98 1.98 971. 551. R 1000 diametβ¦
#> 3 1 11336476 0.00200 1.98 1.98 970. 551. R 1000 diametβ¦
#> 4 1 11336477 0.00300 1.99 1.99 970. 550. R 1000 diametβ¦
#> 5 1 11336478 0.00400 1.99 1.99 971. 548. R 1000 diametβ¦
#> 6 1 11336479 0.00501 1.99 1.99 972. 547. R 1000 diametβ¦
#> 7 1 11336480 0.00601 1.99 1.99 972. 548. R 1000 diametβ¦
#> 8 1 11336481 0.00701 1.99 1.99 972. 548. R 1000 diametβ¦
#> 9 1 11336482 0.00801 1.99 1.99 972. 550. R 1000 diametβ¦
#> 10 1 11336483 0.00901 1.99 1.99 972. 550. R 1000 diametβ¦
#> # βΉ 4,990 more rows
#> # βΉ 11 more variables: pupil_raw <dbl>, pupil_raw_deblink <dbl>,
#> # pupil_raw_deblink_detransient <dbl>,
#> # pupil_raw_deblink_detransient_interpolate <dbl>,
#> # pupil_raw_deblink_detransient_interpolate_lpfilt <dbl>,
#> # pupil_raw_deblink_detransient_interpolate_lpfilt_z <dbl>,
#> # text_unique <chr>, template <chr>, matching_pattern <chr>, β¦
#>
#> $info
#> $info$block_1
#> $info$block_1$calc_baseline
#> [1] FALSE
#>
#> $info$block_1$apply_baseline
#> [1] FALSE
#>
#> $info$block_1$epoch_events
#> [1] "PROBE_START_{trial}"
#>
#> $info$block_1$epoch_limits
#> [1] 0 1
#>
#> $info$block_1$n_epochs
#> [1] 5π‘ Note: You can customize epoch() with trial-level
metadata!
For instance, here, {trial} will not only extract data but also add a
trial column parsed from the event string, which originally took the
form of PROBE_START_22 (where 22 was the trial number embedded
within the event message string we had originally programmed to be sent
as event messages at the start of each probe trial on our PsychoPy /
EyeLink experiment.
#> # A tibble: 5 Γ 4
#> template matching_pattern matched_event trial
#> <chr> <chr> <chr> <chr>
#> 1 PROBE_START_{trial} ^PROBE_START_(.*?)$ PROBE_START_22 22
#> 2 PROBE_START_{trial} ^PROBE_START_(.*?)$ PROBE_START_22 22
#> 3 PROBE_START_{trial} ^PROBE_START_(.*?)$ PROBE_START_22 22
#> 4 PROBE_START_{trial} ^PROBE_START_(.*?)$ PROBE_START_22 22
#> 5 PROBE_START_{trial} ^PROBE_START_(.*?)$ PROBE_START_22 22
Use the 1-second window before
"DELAY_STOP"as a baseline and apply it to the epoch data.
eye_1c <- eye |>
epoch(
events = "PROBE_START_{trial}",
limits = c(0, 1),
label = "probeEpochs",
calc_baseline = TRUE,
apply_baseline = TRUE,
baseline_type = "sub",
baseline_events = "DELAY_STOP_*",
baseline_period = c(-1, 0)
)In this example, weβre extracting 1-second epochs following each
"PROBE_START" event and applying subtractive baseline correction.
The baseline is computed from the 1-second window before each
corresponding "DELAY_STOP" event.
In other words, this means each pupil trace is normalized by subtracting the average pupil size from the pre-probe delay period (i.e., the baseline period).
Manually define start and end times for two trials:
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")
)
eye_1d <- eye |>
epoch(
events = list(start_events, end_events, 1), # 1 = block number
label = "manualTrials"
)Once epoched, your data is ready to be exported with bidsify(), which
saves the raw and epoched data in a structured, BIDS-inspired format.
bidsify(
eyeris = eye_1c,
bids_dir = "~/Documents/eyeris",
participant_id = "001",
session_num = "01",
task_name = "assocmem",
run_num = "01",
save_raw = TRUE, # Also save raw timeseries
html_report = TRUE # Generate a preprocessing summary
)Which will create a directory structure like this:
eyeris
βββ derivatives
βββ sub-001
βββ ses-01
βββ eye
β βββ sub-001_ses-01_task-assocret_run-01_desc-timeseries.csv
β βββ sub-001_ses-01_task-assocret_run-01_desc-preproc_pupil_epoch-prePostProbe_bline-sub-prePostProbe.csv
βββ source
β βββ figures
β βββ task-assocret_run-01
β βββ epoch_prePostProbe
β β βββ run-01_PROBE_START_22_1.png
β β βββ run-01_PROBE_START_22_2.png
β β βββ run-01_PROBE_START_22_3.png
β β βββ run-01_PROBE_START_22_4.png
β β βββ run-01_PROBE_START_22_5.png
β β βββ run-01_PROBE_START_22_6.png
β β βββ ...
β β βββ run-01_PROBE_STOP_22_1.png
β β βββ run-01_PROBE_STOP_22_2.png
β β βββ run-01_PROBE_STOP_22_3.png
β β βββ run-01_PROBE_STOP_22_4.png
β β βββ run-01_PROBE_STOP_22_5.png
β β βββ run-01_PROBE_STOP_22_6.png
β β βββ ...
β βββ task-assocret_run-01_fig-1_desc-histogram.jpg
β βββ task-assocret_run-01_fig-1_desc-timeseries.jpg
βββ sub-001_task-assocret_epoch-prePostProbe_run-01.html
βββ sub-001_task-assocret.html
9 directories, 80 files
See the π QC with Interactive Reports vignette for more details.
This vignette demonstrated how to:
- Load and preprocess raw
.asc(EyeLink) pupil data files usingeyeris. - Extract event-based epochs using both pattern matching and manual timestamps.
- Flexibly apply baseline correction.
- Save out the results in a clean, reproducible, BIDS-like folder structure.
Check out the function documentation for epoch() and bidsify() to
learn more about other customization options that may be useful for your
specific workflow.
If you use the eyeris package in your research, please cite it!
Run the following in R to get the citation:
citation("eyeris")
#> To cite package 'eyeris' in publications use:
#>
#> Schwartz ST, Yang H, Xue AM, He M (2025). "eyeris: A flexible,
#> extensible, and reproducible pupillometry preprocessing framework in
#> R." _bioRxiv_, 1-37. doi:10.1101/2025.06.01.657312
#> <https://doi.org/10.1101/2025.06.01.657312>.
#>
#> A BibTeX entry for LaTeX users is
#>
#> @Article{,
#> title = {eyeris: A flexible, extensible, and reproducible pupillometry preprocessing framework in R},
#> author = {Shawn T Schwartz and Haopei Yang and Alice M Xue and Mingjian He},
#> journal = {bioRxiv},
#> year = {2025},
#> pages = {1--37},
#> doi = {10.1101/2025.06.01.657312},
#> }