├── .Rbuildignore
├── .github
├── .gitignore
└── workflows
│ └── pkgdown.yaml
├── .gitignore
├── DESCRIPTION
├── NAMESPACE
├── NEWS.md
├── R
├── RcppExports.R
├── adjust_message_time.R
├── check_consistency_flag.R
├── check_logical_flag.R
├── check_string_parameter.R
├── check_that_compiled.R
├── compute_cyclopean_samples.R
├── convert_codes.R
├── data.R
├── extract_aois.R
├── extract_blinks.R
├── extract_display_coords.R
├── extract_fixations.R
├── extract_saccades.R
├── extract_triggers.R
├── extract_variables.R
├── eyelinkReader-classes.R
├── eyelinkReader-package.R
├── logical_index_for_sample_attributes.R
├── plot.R
├── print.R
├── read_edf.R
├── read_preamble.R
└── zzz.R
├── README.md
├── _pkgdown.yml
├── data
└── gaze.rda
├── eyelinkReader.Rproj
├── inst
├── WORDLIST
├── cpp
│ └── edf_interface.cpp
└── extdata
│ └── example.edf
├── man
├── adjust_message_time.Rd
├── check_consistency_flag.Rd
├── check_logical_flag.Rd
├── check_string_parameter.Rd
├── check_that_compiled.Rd
├── compiled_library_status.Rd
├── compute_cyclopean_samples.Rd
├── convert_NAs.Rd
├── convert_header_codes.Rd
├── convert_recording_codes.Rd
├── dot-onAttach.Rd
├── dot-onLoad.Rd
├── extract_AOIs.Rd
├── extract_blinks.Rd
├── extract_display_coords.Rd
├── extract_fixations.Rd
├── extract_saccades.Rd
├── extract_triggers.Rd
├── extract_variables.Rd
├── eyelinkReader.Rd
├── eyelinkRecording-class.Rd
├── figures
│ └── logo.svg
├── gaze.Rd
├── logical_index_for_sample_attributes.Rd
├── plot.eyelinkRecording.Rd
├── print.eyelinkRecording.Rd
├── read_edf.Rd
├── read_edf_file.Rd
├── read_preamble.Rd
└── read_preamble_str.Rd
├── pkgdown
└── favicon
│ ├── apple-touch-icon.png
│ ├── favicon-96x96.png
│ ├── favicon.ico
│ ├── favicon.svg
│ ├── site.webmanifest
│ ├── web-app-manifest-192x192.png
│ └── web-app-manifest-512x512.png
├── src
├── .gitignore
├── Makevars
├── Makevars.win
├── RcppExports.cpp
├── compiled_library_status.cpp
├── convert_NAs.cpp
├── read_edf_file.cpp
└── read_preamble_str.cpp
├── tests
├── testthat.R
└── testthat
│ ├── test-safety_checks.R
│ └── test-sample-attributes.R
└── vignettes
├── .gitignore
├── installation.Rmd
├── plotting.Rmd
└── usage.Rmd
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | ^.*\.Rproj$
2 | ^\.Rproj\.user$
3 | ^_pkgdown\.yml$
4 | ^docs$
5 | ^pkgdown$
6 | ^\.github$
7 | eyelinkReader.svg
8 |
--------------------------------------------------------------------------------
/.github/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 |
--------------------------------------------------------------------------------
/.github/workflows/pkgdown.yaml:
--------------------------------------------------------------------------------
1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
3 | on:
4 | push:
5 | branches: [main, master]
6 | pull_request:
7 | release:
8 | types: [published]
9 | workflow_dispatch:
10 |
11 | name: pkgdown.yaml
12 |
13 | permissions: read-all
14 |
15 | jobs:
16 | pkgdown:
17 | runs-on: ubuntu-latest
18 | # Only restrict concurrency for non-PR jobs
19 | concurrency:
20 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }}
21 | env:
22 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
23 | permissions:
24 | contents: write
25 | steps:
26 | - uses: actions/checkout@v4
27 |
28 | - uses: r-lib/actions/setup-pandoc@v2
29 |
30 | - uses: r-lib/actions/setup-r@v2
31 | with:
32 | use-public-rspm: true
33 |
34 | - uses: r-lib/actions/setup-r-dependencies@v2
35 | with:
36 | extra-packages: any::pkgdown, local::.
37 | needs: website
38 |
39 | - name: Build site
40 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE)
41 | shell: Rscript {0}
42 |
43 | - name: Deploy to GitHub pages 🚀
44 | if: github.event_name != 'pull_request'
45 | uses: JamesIves/github-pages-deploy-action@v4.5.0
46 | with:
47 | clean: false
48 | branch: gh-pages
49 | folder: docs
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | .Rhistory
3 | .RData
4 | .Ruserdata
5 | docs
6 | inst/doc
7 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Package: eyelinkReader
2 | Title: Import Gaze Data for EyeLink Eye Tracker
3 | Version: 1.0.3
4 | Date: 2024-10-08
5 | Authors@R:
6 | person(given = "Alexander",
7 | family = "Pastukhov",
8 | role = c("aut", "cre"),
9 | email ="pastukhov.alexander@gmail.com",
10 | comment = c(ORCID = "0000-0002-8738-8591"))
11 | Description: Import gaze data from edf files generated by
12 | the SR Research EyeLink eye tracker. Gaze data,
13 | both recorded events and samples, is imported per trial. The package allows to extract
14 | events of interest, such as saccades, blinks, etc. as well as
15 | recorded variables and custom events (areas of interest, triggers) into separate tables.
16 | The package requires EDF API library that can be obtained at .
17 | License: GPL (>= 3)
18 | URL: https://github.com/alexander-pastukhov/eyelinkReader/,
19 | https://alexander-pastukhov.github.io/eyelinkReader/
20 | BugReports: https://github.com/alexander-pastukhov/eyelinkReader/issues
21 | Depends:
22 | R (>= 4.1.0),
23 | RcppProgress,
24 | rlang
25 | Encoding: UTF-8
26 | NeedsCompilation: yes
27 | VignetteBuilder:
28 | knitr
29 | LazyData: true
30 | LinkingTo: Rcpp, RcppProgress
31 | Imports:
32 | dplyr,
33 | fs,
34 | purrr,
35 | Rcpp,
36 | stringr,
37 | tidyr,
38 | methods,
39 | ggplot2
40 | RoxygenNote: 7.3.2
41 | Roxygen: list(markdown = TRUE)
42 | SystemRequirements: GNU make
43 | Suggests:
44 | rmarkdown,
45 | knitr,
46 | testthat (>= 3.0.0)
47 | Config/testthat/edition: 3
48 |
--------------------------------------------------------------------------------
/NAMESPACE:
--------------------------------------------------------------------------------
1 | # Generated by roxygen2: do not edit by hand
2 |
3 | S3method(adjust_message_time,data.frame)
4 | S3method(adjust_message_time,eyelinkRecording)
5 | S3method(compute_cyclopean_samples,data.frame)
6 | S3method(compute_cyclopean_samples,eyelinkRecording)
7 | S3method(extract_AOIs,data.frame)
8 | S3method(extract_AOIs,eyelinkRecording)
9 | S3method(extract_blinks,data.frame)
10 | S3method(extract_blinks,eyelinkRecording)
11 | S3method(extract_display_coords,data.frame)
12 | S3method(extract_display_coords,eyelinkRecording)
13 | S3method(extract_fixations,data.frame)
14 | S3method(extract_fixations,eyelinkRecording)
15 | S3method(extract_saccades,data.frame)
16 | S3method(extract_saccades,eyelinkRecording)
17 | S3method(extract_triggers,data.frame)
18 | S3method(extract_triggers,eyelinkRecording)
19 | S3method(extract_variables,data.frame)
20 | S3method(extract_variables,eyelinkRecording)
21 | S3method(plot,eyelinkRecording)
22 | S3method(print,eyelinkPreamble)
23 | S3method(print,eyelinkRecording)
24 | export(.onAttach)
25 | export(.onLoad)
26 | export(adjust_message_time)
27 | export(check_consistency_flag)
28 | export(check_logical_flag)
29 | export(check_string_parameter)
30 | export(check_that_compiled)
31 | export(compiled_library_status)
32 | export(compute_cyclopean_samples)
33 | export(convert_NAs)
34 | export(convert_header_codes)
35 | export(convert_recording_codes)
36 | export(extract_AOIs)
37 | export(extract_blinks)
38 | export(extract_display_coords)
39 | export(extract_fixations)
40 | export(extract_saccades)
41 | export(extract_triggers)
42 | export(extract_variables)
43 | export(logical_index_for_sample_attributes)
44 | export(read_edf)
45 | export(read_edf_file)
46 | export(read_preamble)
47 | export(read_preamble_str)
48 | import(Rcpp)
49 | import(RcppProgress)
50 | importFrom(Rcpp,evalCpp)
51 | importFrom(dplyr,"%>%")
52 | importFrom(dplyr,all_of)
53 | importFrom(dplyr,arrange)
54 | importFrom(dplyr,filter)
55 | importFrom(dplyr,mutate)
56 | importFrom(dplyr,mutate_if)
57 | importFrom(dplyr,select)
58 | importFrom(fs,file_exists)
59 | importFrom(ggplot2,aes_string)
60 | importFrom(ggplot2,coord_equal)
61 | importFrom(ggplot2,geom_point)
62 | importFrom(ggplot2,geom_segment)
63 | importFrom(ggplot2,ggplot)
64 | importFrom(ggplot2,labs)
65 | importFrom(ggplot2,scale_x_continuous)
66 | importFrom(ggplot2,scale_y_reverse)
67 | importFrom(methods,hasArg)
68 | importFrom(methods,is)
69 | importFrom(purrr,map_chr)
70 | importFrom(purrr,map_int)
71 | importFrom(rlang,.data)
72 | importFrom(stringr,str_detect)
73 | importFrom(stringr,str_extract)
74 | importFrom(stringr,str_remove)
75 | importFrom(stringr,str_remove_all)
76 | importFrom(stringr,str_replace)
77 | importFrom(stringr,str_split)
78 | importFrom(tidyr,separate)
79 | useDynLib(eyelinkReader)
80 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | # eyelinkReader 1.0.0
2 | * Initial CRAN Release
3 |
4 | # eyelinkReader 1.0.1
5 | ## Enhancements
6 | * Optional GRUB for plotting
7 |
8 | ## Bug Fixes
9 | * Internal documentation structure changes.
10 |
11 | # eyelinkReader 1.0.2
12 | ## Bug Fixes
13 | * Correct path for compilation on Mac
14 | * Bug fix in plot function
15 | * UTF-8 encoding for event message strings
16 | * AOI parsing function now allows for labels with white spaces
17 |
18 | # eyelinkReader 1.0.3
19 | ## Bug Fixes
20 | * Format string for the error message
21 |
--------------------------------------------------------------------------------
/R/RcppExports.R:
--------------------------------------------------------------------------------
1 | # Generated by using Rcpp::compileAttributes() -> do not edit by hand
2 | # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
3 |
4 | #' @title Status of compiled library
5 | #' @description Return status of compiled library
6 | #' @return logical
7 | #' @export
8 | #' @examples
9 | #' compiled_library_status()
10 | compiled_library_status <- function() {
11 | .Call('_eyelinkReader_compiled_library_status', PACKAGE = 'eyelinkReader')
12 | }
13 |
14 | #' @title Convert -32767 (missing info) to NA
15 | #' @description Converts all -32767 (smallest INT16 value indicating missing info) to NA.
16 | #' You don't need to call this function directly, as it is automatically evoked within
17 | #' \code{\link{read_edf}} function.
18 | #' @param original_frame data.frame to be processed
19 | #' @return processed data.frame
20 | #' @export
21 | #' @examples
22 | #' \donttest{
23 | #' data(gaze)
24 | #' gaze$samples <- convert_NAs(gaze$samples)
25 | #' }
26 | convert_NAs <- function(original_frame) {
27 | .Call('_eyelinkReader_convert_NAs', PACKAGE = 'eyelinkReader', original_frame)
28 | }
29 |
30 | #' @title Internal function that reads EDF file
31 | #' @description Reads EDF file into a list that contains events, samples, and recordings.
32 | #' DO NOT call this function directly. Instead, use read_edf function that implements
33 | #' parameter checks and additional postprocessing.
34 | #' @param filename full name of the EDF file
35 | #' @param consistency consistency check control (for the time stamps of the start
36 | #' and end events, etc). 0, no consistency check. 1, check consistency and report.
37 | #' 2, check consistency and fix.
38 | #' @param import_events load/skip loading events.
39 | #' @param import_recordings load/skip loading recordings.
40 | #' @param import_samples load/skip loading of samples.
41 | #' @param sample_attr_flag boolean vector that indicates which sample fields are to be stored
42 | #' @param start_marker_string event that marks trial start. Defaults to "TRIALID", if empty.
43 | #' @param end_marker_string event that marks trial end
44 | #' @param verbose whether to show progressbar and report number of trials
45 | #' @export
46 | #' @keywords internal
47 | #' @return contents of the EDF file. Please see read_edf for details.
48 | read_edf_file <- function(filename, consistency, import_events, import_recordings, import_samples, sample_attr_flag, start_marker_string, end_marker_string, verbose) {
49 | .Call('_eyelinkReader_read_edf_file', PACKAGE = 'eyelinkReader', filename, consistency, import_events, import_recordings, import_samples, sample_attr_flag, start_marker_string, end_marker_string, verbose)
50 | }
51 |
52 | #' @title Reads preamble of the EDF file as a single string.
53 | #' @description Reads preamble of the EDF file as a single string.
54 | #' Please, do not use this function directly. Instead, call \code{\link{read_preamble}} function
55 | #' that provides a more consistent interface.
56 | #' @return string with the preamble
57 | #' @export
58 | #' @keywords internal
59 | #' @examples
60 | #' \donttest{
61 | #' if (eyelinkReader::compiled_library_status()) {
62 | #' read_preamble(system.file("extdata", "example.edf", package = "eyelinkReader"))
63 | #' }
64 | #' }
65 | read_preamble_str <- function(filename) {
66 | .Call('_eyelinkReader_read_preamble_str', PACKAGE = 'eyelinkReader', filename)
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/R/adjust_message_time.R:
--------------------------------------------------------------------------------
1 | #' Adjusts message time based on embedded text offset
2 | #'
3 | #' Uses text in the message to adjust its time. E.g.,
4 | #' for a message \code{"-50 TARGET_ONSET"} that was sent at \code{105600}
5 | #' the actual onset occurred 50 milliseconds earlier (\code{-50}). The function
6 | #' adjusts the event timing and removes the timing offset information from
7 | #' the message. I.e., the example message becomes \code{"TARGET_ONSET"} and
8 | #' its time become \code{105550}.
9 | #'
10 | #' @param object An \code{\link{eyelinkRecording}} object or data.frame with events,
11 | #' i.e., \code{events} slot of the \code{\link{eyelinkRecording}} object.
12 | #' @param prefix String with a regular expression that defines the offset.
13 | #' Defaults to \code{"^[-+]?[:digit:]+[:space:]+"} (a string starts with a positive
14 | #' or negative integer offset followed by a white space and the rest of the message).
15 | #'
16 | #' @return Object of the same time as input, i.e., either a \code{\link{eyelinkRecording}} object
17 | #' with \emph{modified} \code{events} slot or a data.frame with offset-adjusted events.
18 | #' @export
19 | #'
20 | #' @examples
21 | #' data(gaze)
22 | #'
23 | #' # by passing events table
24 | #' adjusted_events <- adjust_message_time(gaze$events)
25 | #'
26 | #' # by passing the recording
27 | #' gaze <- adjust_message_time(gaze)
28 |
29 | adjust_message_time <- function(object, prefix) { UseMethod("adjust_message_time") }
30 |
31 | #' @rdname adjust_message_time
32 | #' @importFrom stringr str_detect str_extract str_replace
33 | #' @importFrom dplyr arrange
34 | #' @export
35 | adjust_message_time.data.frame <- function(object, prefix = "^[-+]?[:digit:]+[:space:]+"){
36 | # find messages that need adjusting
37 | need_adjusting <- which(stringr::str_detect(object$message, prefix))
38 |
39 | if (length(need_adjusting) > 0) {
40 | # get temporal offset as a number
41 | offset <- as.numeric(str_extract(object$message[need_adjusting], prefix))
42 |
43 | # remove offset from the message
44 | object$message[need_adjusting] <- stringr::str_replace(object$message[need_adjusting], prefix, "")
45 |
46 | # adjust time
47 | object$sttime[need_adjusting] <- object$sttime[need_adjusting] + offset
48 | object$sttime_rel[need_adjusting] <- object$sttime_rel[need_adjusting] + offset
49 |
50 | # sort events based on adjusted time
51 | object <- dplyr::arrange(object, .data$sttime)
52 | }
53 |
54 | object
55 | }
56 |
57 | #' @rdname adjust_message_time
58 | #' @export
59 | adjust_message_time.eyelinkRecording <- function(object, prefix = "^[-+]?[:digit:]+[:space:]+"){
60 | # check that events are in the recording at all
61 | if (!("events" %in% names(object))) {
62 | warning("No events in an eyelinkRecording object, nothing to do.")
63 | return(object)
64 | }
65 |
66 | # modify in place
67 | object$events <- adjust_message_time(object$events, prefix)
68 | object
69 | }
70 |
--------------------------------------------------------------------------------
/R/check_consistency_flag.R:
--------------------------------------------------------------------------------
1 | #' Checks consistency flag, stops if invalid, returns code if valid.
2 | #'
3 | #' @param consistency consistency check control for the time stamps of the start
4 | #' and end events, etc. Could be \code{'no consistency check'} (0),
5 | #' \code{'check consistency and report'} (1), \code{'check consistency and fix'} (2).
6 | #'
7 | #' @return integer index
8 | #' @export
9 | #' @keywords internal
10 | #'
11 | #' @examples
12 | #' check_consistency_flag('no consistency check')
13 | check_consistency_flag <- function(consistency) {
14 | # converting consistency to integer constant that C-code understands
15 | requested_consistency <- factor(consistency,
16 | levels= c('no consistency check', 'check consistency and report', 'check consistency and fix'))
17 | if (length(requested_consistency) < 1) stop("Null value for consistency flag")
18 | if (length(requested_consistency) > 1) stop("Multiple values for consistency flag")
19 | if (is.na(requested_consistency)) stop(sprintf('Bad consistency check value "%s".', consistency))
20 |
21 | # zero-based index
22 | as.numeric(requested_consistency) - 1
23 | }
24 |
--------------------------------------------------------------------------------
/R/check_logical_flag.R:
--------------------------------------------------------------------------------
1 | #' Checks for validity of a logical flag, stops if not valid
2 | #'
3 | #' @param logical_flag Logical scalar
4 | #'
5 | #' @return logical, value of the logical_flag
6 | #' @export
7 | #' @keywords internal
8 | #'
9 | #' @examples
10 | #' import_samples <- TRUE
11 | #' check_logical_flag(import_samples)
12 | check_logical_flag <- function(logical_flag){
13 | param_name <- as.character(match.call()[[2]])
14 | if (length(logical_flag) != 1) stop(sprintf("%s must be a scalar logical value", param_name))
15 | if (is.null(logical_flag)) stop(sprintf("%s must be a scalar logical value, not NULL", param_name))
16 | if (!is.logical(logical_flag)) stop(sprintf("%s must be a scalar logical value", param_name))
17 | if (is.na(logical_flag)) stop(sprintf("%s must be a scalar logical value, NA not allowed", param_name))
18 |
19 | logical_flag
20 | }
21 |
--------------------------------------------------------------------------------
/R/check_string_parameter.R:
--------------------------------------------------------------------------------
1 | #' Checks for validity of a string parameter, stops if not valid
2 | #'
3 | #' @param string_parameter String scalar
4 | #'
5 | #' @return character, value of the string_parameter
6 | #' @export
7 | #' @keywords internal
8 | #'
9 | #' @examples
10 | #' start_marker <- "TRIALID"
11 | #' check_string_parameter(start_marker)
12 | check_string_parameter <- function(string_parameter){
13 | param_name <- as.character(match.call()[[2]])
14 | if (length(string_parameter) != 1) stop(sprintf("%s must be a scalar string value", param_name))
15 | if (is.null(string_parameter)) stop(sprintf("%s must be a scalar logical value, not NULL", param_name))
16 | if (!is.character(string_parameter)) stop(sprintf("%s must be a scalar string value", param_name))
17 | if (nchar(string_parameter) == 0) warning(sprintf("Empty string for %s parameter", param_name))
18 | if (is.na(string_parameter)) stop(sprintf("%s must be a scalar logical value, NA not allowed", param_name))
19 |
20 | string_parameter
21 | }
22 |
--------------------------------------------------------------------------------
/R/check_that_compiled.R:
--------------------------------------------------------------------------------
1 | #' Checks whether EDF API library was present and
2 | #' interface was successfully be compiled
3 | #'
4 | #' @param fail_loudly logical, whether lack of compiled library means
5 | #' error (\code{TRUE}), just warning (\code{FALSE}), or silent (\code{NA},
6 | #' for test use only).
7 | #'
8 | #' @export
9 | #' @return No return value. Stops the computation, if compiled interface to EDF API in missing.
10 | #' @keywords internal
11 | #'
12 | #' @examples
13 | #' check_that_compiled(fail_loudly = FALSE)
14 | check_that_compiled <- function(fail_loudly = TRUE){
15 | # try to compile
16 | if (!eyelinkReader::compiled_library_status() && !is.na(fail_loudly)) {
17 | if (fail_loudly) {
18 | stop("Failed to compile interface to EDF API, function will return NULL. Please read the manual for further details.")
19 | } else {
20 | warning("Failed to compile interface to EDF API, function will return NULL. Please read the manual for further details.")
21 | }
22 | }
23 |
24 | eyelinkReader::compiled_library_status()
25 | }
26 |
--------------------------------------------------------------------------------
/R/compute_cyclopean_samples.R:
--------------------------------------------------------------------------------
1 | #' Computes cyclopean samples by averaging over binocular data
2 | #'
3 | #' Computes cyclopean samples by averaging over binocular recorded properties
4 | #' such as \code{pxL}/\code{pxR}, \code{pyL}/\code{pyR}, \code{hxL}/\code{hxR},
5 | #' etc. Uses function specified via \code{fun}
6 | #' parameter to compute the average with \code{na.rm = TRUE} option.
7 | #' In case of a monocular recording or when the information from one eye missing,
8 | #' uses information from one eye only, ignoring the other column.
9 | #' In both binocular and monocular recording cases, simplifies column names
10 | #' so that \code{pxL} and/or \code{pxR} are replaced
11 | #' with a single column \code{px}, \code{pyL}/\code{pyR} with \code{py}, etc.
12 | #'
13 | #' @param object Either an \code{\link{eyelinkRecording}} object or data.frame with samples,
14 | #' i.e., \code{samples} slot of the \code{\link{eyelinkRecording}} object.
15 | #' @param fun Function used to average across eyes, defaults to \code{\link{mean}}.
16 | #'
17 | #' @return Object of the same time as input, i.e., either a \code{\link{eyelinkRecording}} object
18 | #' with \emph{modified} \code{samples} slot or a data.frame with cyclopean samples.
19 |
20 | #' @export
21 | #'
22 | #' @examples
23 | #' data(gaze)
24 | #'
25 | #' # by passing samples table
26 | #' cyclopean_samples <- compute_cyclopean_samples(gaze$samples)
27 | #'
28 | #' # storing cyclopean samples as a separate table in recording
29 | #' gaze$cyclopean_samples <- compute_cyclopean_samples(gaze$samples)
30 | #'
31 | #' # by passing the recording, cyclopean samples replace original ones
32 | #' gaze <- compute_cyclopean_samples(gaze)
33 | compute_cyclopean_samples <- function(object, fun = mean) { UseMethod("compute_cyclopean_samples") }
34 |
35 |
36 | #' @rdname compute_cyclopean_samples
37 | #' @export
38 | #' @importFrom dplyr %>% select all_of mutate_if
39 | #' @importFrom tidyr separate
40 | #' @importFrom stringr str_detect str_remove
41 | #' @importFrom rlang .data
42 | compute_cyclopean_samples.data.frame <- function(object, fun = mean) {
43 | # figuring out columns that we need to compute the mean over
44 | i_eye_specific_column <- stringr::str_detect(names(object), "[L|R]$")
45 | eye_specific_columns <- names(object)[i_eye_specific_column]
46 | joint_eye_columns <- unique(stringr::str_remove(eye_specific_columns, "[L|R]$"))
47 | resulting_columns <- unique(stringr::str_remove(names(object), "[L|R]$"))
48 |
49 | # averaging over eye-specific columns
50 | for(current_column in joint_eye_columns){
51 | eye_components <- eye_specific_columns[stringr::str_detect(eye_specific_columns, sprintf("^%s[L|R]$", current_column))]
52 | if (identical(fun, mean)) {
53 | # rowMeans are MUCH faster than applying mean to margin row
54 | object[[current_column]] <- rowMeans(object[, eye_components], na.rm = TRUE)
55 | } else {
56 | # universal solution for everything else
57 | object[[current_column]] <- apply(object[, eye_components], MARGIN = 1, FUN = fun, na.rm = TRUE)
58 | }
59 | }
60 |
61 | object %>%
62 | # drop original eye-specific columns
63 | select(all_of(resulting_columns)) %>%
64 |
65 | # convert NaN to NA because mean(c(NA, NA), na.rm = TRUE)) returns NaN, not NA
66 | mutate_if(is.numeric, ~ifelse(is.nan(.x), NA, .x))
67 | }
68 |
69 |
70 | #' @rdname compute_cyclopean_samples
71 | #' @export
72 | compute_cyclopean_samples.eyelinkRecording <- function(object, fun = mean) {
73 | # check that samples are in the recording at all
74 | if (!("samples" %in% names(object))) {
75 | stop("No samples in an eyelinkRecording object.")
76 | }
77 |
78 | # modify in place
79 | object$samples <- compute_cyclopean_samples(object$samples, fun)
80 | object
81 | }
82 |
--------------------------------------------------------------------------------
/R/convert_codes.R:
--------------------------------------------------------------------------------
1 | #' Converts integer constants in trial headers to factor with explicit labels
2 | #'
3 | #' @description Converts integer constants in trial headers to factor with explicit labels.
4 | #' Please refer to EDF API manual for further details.
5 | #' @param trial_headers data.frame that contains trial headers.
6 | #'
7 | #' @return a modified trial_headers table
8 | #' @keywords internal
9 | #' @export
10 | convert_header_codes <- function(trial_headers){
11 | trial_headers$rec_state <- factor(trial_headers$rec_state, levels = c(0, 1), labels= c('END', 'START'))
12 | trial_headers$rec_record_type <- factor(trial_headers$rec_record_type, levels = c(1, 2, 3), labels= c('SAMPLES', 'EVENTS', 'SAMPLES and EVENTS'))
13 | trial_headers$rec_pupil_type <- factor(trial_headers$rec_pupil_type, levels = c(0, 1), labels= c('AREA', 'DIAMETER'))
14 | trial_headers$rec_recording_mode <- factor(trial_headers$rec_recording_mode, levels = c(0, 1), labels= c('PUPIL', 'CR'))
15 | # trial_headers$rec_pos_type <- factor(trial_headers$rec_pos_type, levels = c(0, 1, 2), labels= c('GAZE', 'HREF', 'RAW'))
16 | trial_headers$rec_eye <- factor(trial_headers$rec_eye, levels = c(1, 2, 3), labels= c('LEFT', 'RIGHT', 'LEFT and RIGHT'))
17 |
18 | return (trial_headers);
19 | }
20 |
21 | #' Converts integer constants in recordings to factor with explicit labels
22 | #'
23 | #' @description Converts integer constants in trial recordings information to factor with explicit labels.
24 | #' Please refer to EDF API manual for further details.
25 | #' @param trial_recordings data.frame that contains trial recordings.
26 | #'
27 | #' @return a modified trial_recordings table
28 | #' @keywords internal
29 | #' @export
30 | convert_recording_codes <- function(trial_recordings){
31 | trial_recordingsstate <- factor(trial_recordings$state, levels = c(0, 1), labels= c('END', 'START'))
32 | trial_recordings$record_type <- factor(trial_recordings$record_type, levels = c(1, 2, 3), labels= c('SAMPLES', 'EVENTS', 'SAMPLES and EVENTS'))
33 | trial_recordings$pupil_type <- factor(trial_recordings$pupil_type, levels = c(0, 1), labels= c('AREA', 'DIAMETER'))
34 | trial_recordings$recording_mode <- factor(trial_recordings$recording_mode, levels = c(0, 1), labels= c('PUPIL', 'CR'))
35 | # trial_recordings$pos_type <- factor(trial_recordings$pos_type, levels = c(0, 1, 2), labels= c('GAZE', 'HREF', 'RAW'))
36 | trial_recordings$eye <- factor(trial_recordings$eye, levels = c(1, 2, 3), labels= c('LEFT', 'RIGHT', 'LEFT and RIGHT'))
37 |
38 | return (trial_recordings);
39 | }
40 |
--------------------------------------------------------------------------------
/R/data.R:
--------------------------------------------------------------------------------
1 | #' Imported example.edf, events and samples
2 | #'
3 | #' An \code{\link{eyelinkRecording}} for _example.edf_ via
4 | #' \code{read_edf(system.file("extdata", "example.edf", package = "eyelinkReader"), import_samples = TRUE)}).
5 | #' Contains all extracted events including triggers, areas of interested, and display coordinates. The original
6 | #' recording consist of ten trials with a participant fixating on a target that jumps to a new location after
7 | #' one second and stays on the screen for another second. Includes all relevant events.
8 | #'
9 | #' See \code{\link{eyelinkRecording}} for details.
10 | #' @seealso \code{\link{eyelinkRecording}}, \code{\link{read_edf}}
11 | "gaze"
12 |
--------------------------------------------------------------------------------
/R/extract_aois.R:
--------------------------------------------------------------------------------
1 | #' Extracts rectangular areas of interest (AOI)
2 | #'
3 | #' @description Extracts rectangular areas of interest (AOI),
4 | #' as defined by \code{"!V IAREA RECTANGLE"} command.
5 | #' Specifically, we expect it to be in format
6 | #' \code{!V IAREA RECTANGLE