
Integrated signal-preprocessing workflow
Source:vignettes/articles/signal-preprocessing-workflow.Rmd
signal-preprocessing-workflow.RmdScope
preprocess_gazepoint_signals() coordinates the
lightweight signal-processing helpers in a fixed, reviewable order. The
workflow preserves original columns, adds processed columns, records
each operation, and returns blink and fixation event tables
separately.
The function is intended for transparent preprocessing and methodological sensitivity analysis. It does not imply that one detector or preprocessing configuration recovers a uniquely true sequence of eye events.
Synthetic sample-level data
The example is synthetic and contains no participant records.
set.seed(1)
n <- 180L
time <- seq(0, by = 1 / 60, length.out = n)
signal <- data.frame(
USER_ID = rep("P01", n),
trial = rep(c("T01", "T02"), each = n / 2),
TIME = time,
FPOGX = c(
rep(0.25, 55),
seq(0.25, 0.75, length.out = 10),
rep(0.75, 55),
seq(0.75, 0.35, length.out = 10),
rep(0.35, 50)
),
FPOGY = 0.50 + stats::rnorm(n, 0, 0.002),
LPupil = 3.2 + stats::rnorm(n, 0, 0.03),
RPupil = 3.1 + stats::rnorm(n, 0, 0.03),
stringsAsFactors = FALSE
)
signal$LPupil[35:40] <- NA_real_
signal$RPupil[35:40] <- NA_real_
signal$LPupil[130:134] <- NA_real_
signal$RPupil[130:134] <- NA_real_
utils::head(signal)
#> USER_ID trial TIME FPOGX FPOGY LPupil RPupil
#> 1 P01 T01 0.00000000 0.25 0.4987471 3.163060 3.022230
#> 2 P01 T01 0.01666667 0.25 0.5003673 3.229517 3.139420
#> 3 P01 T01 0.03333333 0.25 0.4983287 3.206598 3.080934
#> 4 P01 T01 0.05000000 0.25 0.5031906 3.155982 3.087101
#> 5 P01 T01 0.06666667 0.25 0.5006590 3.215631 3.094920
#> 6 P01 T01 0.08333333 0.25 0.4983591 3.195237 3.118367Run the workflow
result <- preprocess_gazepoint_signals(
data = signal,
id_col = "USER_ID",
group_cols = "trial",
time_col = "TIME",
x_col = "FPOGX",
y_col = "FPOGY",
left_pupil_col = "LPupil",
right_pupil_col = "RPupil",
pupil_mode = "mean",
detect_blinks = TRUE,
interpolate_blinks = TRUE,
smooth_pupil = TRUE,
smooth_coordinates = TRUE,
downsample_factor = 2,
detect_fixations = TRUE,
blink_args = list(
min_duration = 30,
include_rapid_changes = FALSE
),
fixation_args = list(
vmax = 5,
min_duration = 60
)
)Decision log
Every requested operation is represented explicitly.
result$decision_log
#> step operation requested status input_rows output_rows
#> 1 1 binocular_pupil_mean TRUE applied 180 180
#> 2 2 blink_detection TRUE applied 180 180
#> 3 3 blink_interpolation TRUE applied 180 180
#> 4 4 pupil_smoothing TRUE applied 180 180
#> 5 5 coordinate_smoothing TRUE applied 180 180
#> 6 6 velocity_fixation_detection TRUE applied 180 180
#> 7 7 downsampling TRUE applied 180 90
#> details
#> 1 LPupil + RPupil
#> 2 2 blink interval(s)
#> 3 Output pupil column: gp3_pupil_fused_blink_interp
#> 4 Output pupil column: pupil_smoothed
#> 5 FPOGX_smooth, FPOGY_smooth
#> 6 2 fixation event(s)
#> 7 Aggregation factor: 2The log records whether an operation was applied or skipped, its input and output row counts, and a compact description of the resolved action.
Processed data
names(result$data)
#> [1] "USER_ID" "trial"
#> [3] "TIME" "FPOGX"
#> [5] "FPOGY" "LPupil"
#> [7] "RPupil" "gp3_pupil_fused"
#> [9] "blink_detected" "blink_id"
#> [11] "blink_reason" "blink_masked"
#> [13] "blink_interpolated" "gp3_pupil_fused_blink_interp"
#> [15] "pupil_smoothed" "pupil_smoothing_status"
#> [17] "pupil_smoothing_window_n" "pupil_smoothing_input_column"
#> [19] "pupil_smoothing_time_column" "pupil_smoothing_method"
#> [21] "pupil_smoothing_align" "pupil_smoothing_window_samples"
#> [23] "pupil_smoothing_min_points" "pupil_smoothing_preserve_missing"
#> [25] "FPOGX_smooth" "FPOGY_smooth"
#> [27] "n_samples_aggregated" "downsample_factor"
utils::head(
result$data[
c(
"USER_ID",
"trial",
"TIME",
"LPupil",
"RPupil",
"gp3_pupil_fused",
"gp3_pupil_fused_blink_interp",
"pupil_smoothed",
"FPOGX",
"FPOGX_smooth"
)
]
)
#> # A tibble: 6 × 10
#> USER_ID trial TIME LPupil RPupil gp3_pupil_fused gp3_pupil_fused_blink_in…¹
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 P01 T01 0.00833 3.16 3.02 3.14 3.09
#> 2 P01 T01 0.0417 3.21 3.08 3.13 3.14
#> 3 P01 T01 0.075 3.22 3.09 3.16 3.16
#> 4 P01 T01 0.108 3.24 3.12 3.16 3.18
#> 5 P01 T01 0.142 3.19 3.08 3.13 3.13
#> 6 P01 T01 0.175 3.19 3.09 3.15 3.14
#> # ℹ abbreviated name: ¹gp3_pupil_fused_blink_interp
#> # ℹ 3 more variables: pupil_smoothed <dbl>, FPOGX <dbl>, FPOGX_smooth <dbl>Original columns remain available. Downsampling reduces rows only
when downsample_factor is larger than one.
Blink intervals
result$blinks
#> # A tibble: 2 × 10
#> USER_ID trial blink_id start_time end_time duration duration_ms n_samples
#> <chr> <chr> <int> <dbl> <dbl> <dbl> <dbl> <int>
#> 1 P01 T01 1 0.567 0.65 100 100 6
#> 2 P01 T02 1 2.15 2.22 83.3 83.3 5
#> # ℹ 2 more variables: reason <chr>, pupil_columns <chr>
result$diagnostics$blink_summary
#> reason n_blinks mean_duration_ms max_duration_ms
#> 1 missing 2 91.66667 100Blink detection is heuristic and should be reviewed against the recording context, sampling rate, pupil scale, and missing-data pattern.
Velocity-based fixation events
utils::head(result$fixations)
#> # A tibble: 2 × 14
#> USER_ID trial fixation_id start_time end_time duration duration_ms n_samples
#> <chr> <chr> <int> <dbl> <dbl> <dbl> <dbl> <int>
#> 1 P01 T01 1 0 1.48 1500 1500 90
#> 2 P01 T02 1 1.5 2.98 1500 1500 90
#> # ℹ 6 more variables: mean_x <dbl>, mean_y <dbl>, median_velocity <dbl>,
#> # max_velocity <dbl>, velocity_threshold <dbl>, algorithm <chr>
result$diagnostics$fixation_summary
#> algorithm n_fixations mean_duration_ms median_duration_ms
#> 1 I-VT 2 1500 1500The fixation table is computed from the full-resolution coordinate series before optional downsampling.
Diagnostic overview
result$diagnostics$overview
#> original_rows full_resolution_processed_rows returned_rows original_columns
#> 1 180 180 90 7
#> returned_columns n_blinks n_fixations pupil_mode final_pupil_col
#> 1 28 2 2 mean pupil_smoothed
#> fixation_x_col fixation_y_col downsample_factor workflow_status
#> 1 FPOGX_smooth FPOGY_smooth 2 ok
result$diagnostics$signal_summary
#> stage n_rows finite_pupil finite_x finite_y
#> 1 original 180 NA NA NA
#> 2 full_resolution_processed 180 180 180 180
#> 3 returned 90 90 90 90Visual audit
plot(
signal$TIME,
signal$LPupil,
type = "l",
xlab = "Time",
ylab = "Pupil",
main = "Synthetic pupil preprocessing"
)
lines(
result$data$TIME,
result$data$pupil_smoothed,
lwd = 2
)
legend(
"topright",
legend = c("Original left pupil", "Processed pupil"),
lty = 1,
lwd = c(1, 2),
bty = "n"
)
plot(
signal$TIME,
signal$FPOGX,
type = "l",
xlab = "Time",
ylab = "Horizontal gaze coordinate",
main = "Synthetic coordinate smoothing"
)
lines(
result$data$TIME,
result$data$FPOGX_smooth,
lwd = 2
)
legend(
"topright",
legend = c("Original", "Smoothed"),
lty = 1,
lwd = c(1, 2),
bty = "n"
)
Alternative specifications
Use pupil_mode = "regression" for the cross-eye
regression helper, or pupil_mode = "none" with an explicit
pupil_col when the input already contains a selected pupil
trace. Override lists expose the underlying helper settings without
changing workflow-managed data, identifier, or output-column
arguments.
Report the operation order, blink criteria, interpolation method and maximum gap, smoothing windows, downsampling factor, coordinate scale, velocity threshold, minimum fixation duration, and any sensitivity specifications.