Skip to contents

Generates a synthetic two-image forced-choice (2IFC) dataset that is shape-compatible with every check_*(), run_diagnostics(), ci_from_responses_2ifc(), and reliability/discriminability function in the package. Useful as a quickstart sandbox and as a building block for simulation studies (power, calibration of reliability and discriminability metrics, sensitivity to contamination).

The function generates the noise pool on the fly via rcicr::generateNoisePattern() and rcicr::generateNoiseImage(), so it requires the rcicr package to be installed. With default arguments (50 participants per condition, 500 trials, 256-pixel images) the noise generation step takes one to a few minutes; a progress bar is shown.

Usage

simulate_2ifc_data(
  n_per_condition = 50L,
  conditions = c("target", "control"),
  n_trials = 500L,
  img_size = 256L,
  base_image = NULL,
  signal_strength = "weak",
  signal_region = "eyes",
  rt_contamination_fast = 0.02,
  rt_contamination_slow = 0.02,
  noise_type = "sinusoid",
  nscales = 5L,
  sigma = 25,
  rdata_dir = NULL,
  seed = NULL,
  progress = TRUE
)

Arguments

n_per_condition

Integer. Participants per condition. Default 50.

conditions

Character vector. Default c("target", "control").

n_trials

Integer. Trials per participant; equals the noise pool size (each pool item shown once per participant). Default 500.

img_size

Integer. Side length of square images, in pixels. Default 256 (matches the bundled base face). Setting this higher (e.g. 512) requires you to also pass a matching base_image.

base_image

Path to a square PNG, or a numeric matrix in [0, 1] of dimension img_size x img_size. Default NULL uses the bundled inst/extdata/sim_base_face.png (a 256x256 grayscale artificial face).

signal_strength

One of "none", "weak", "strong", or a numeric coefficient (the beta in the logistic above). Default "weak".

signal_region

Region passed to make_face_mask(). Default "eyes".

rt_contamination_fast, rt_contamination_slow

Numeric in [0, 1]. Fraction of trials replaced by uniform-fast (50-200 ms) and uniform-slow (5000-20000 ms) contaminants. Default 0.02 each.

noise_type, nscales, sigma

Forwarded to rcicr::generateNoisePattern(). Defaults match rcicr's defaults ("sinusoid", 5, 25).

rdata_dir

Optional directory in which to write the rcicr-format .Rdata stimuli file with a stable filename (rcisignal_sim_2ifc_stimuli.Rdata). When NULL (default) the file goes to a session tempdir and the returned $rdata_path becomes invalid after the R session ends. Pass an explicit directory to persist the simulation across sessions. See Details.

seed

Integer or NULL. When NULL, a random seed is drawn and stored on the result so the run is reproducible.

progress

Logical. Show a cli progress bar during noise generation. Default TRUE.

Value

An object of class "rcisignal_sim" with elements:

  • data — a data.table::data.table with one row per trial and columns participant_id, condition, trial, stimulus, response, rt. Compatible with run_diagnostics() and ci_from_responses_2ifc().

  • noise_matrix(img_size^2) x n_trials numeric matrix.

  • base_faceimg_size x img_size numeric matrix.

  • paramsn_trials x ncoef matrix of per-trial sinusoid coefficients (the rcicr stimuli_params).

  • p — the rcicr noise basis (generateNoisePattern() output); pair with params to regenerate any noise image.

  • rdata_path — path to an rcicr-format .Rdata file written either to a session tempdir (when rdata_dir = NULL) or to the user-supplied rdata_dir. Suitable for ci_from_responses_2ifc() / compute_infoval_summary() and other downstream functions that take an rdata argument. Not portable across R sessions when rdata_dir = NULL.

  • base_image_path — path to a standalone PNG of base_face written next to the .Rdata (rcisignal_sim_2ifc_base_face.png for 2IFC, rcisignal_sim_briefrc_base_face.png for Brief-RC). Same persistence story as rdata_path: persists when rdata_dir is supplied, lives in a session tempdir otherwise. Most users prefer the matrix form base_face directly; the path is provided for symmetry with rcicr's standard workflow.

  • stimuli — a self-contained list (base_face, params, p, etc.) that downstream consumers accept via their stimuli = argument as a portable alternative to rdata_path. Round-trips through saveRDS()/readRDS().

  • signal — pixel-level signal vector used to plant the response bias.

  • meta — list of method, n_per_condition, conditions, n_trials, img_size, signal_strength, signal_region, seed, elapsed_secs.

Details

When rdata_dir = NULL, the returned $rdata_path points at a session tempdir and becomes invalid after the R session ends. Persist the simulation across sessions (caching with saveRDS(), knitr cache=TRUE, sharing with collaborators) either by passing an explicit rdata_dir such as "simdata/", or by handing the returned $stimuli list to ci_from_responses_2ifc() (and the other infoval-dependent helpers) in place of rdata_path. $stimuli is a self-contained in-memory representation of the rcicr stimuli env, so the sim object round-trips through saveRDS()/readRDS() without a file dependency.

Signal model

Each trial t shows two stimuli, image_a = base + noiset and image_b = base - noiset. The participant chooses one. With signal_strength = "none" choices are uniform random (P(+1) = 0.5); with "weak" / "strong" (or a custom numeric beta), the log-odds of choosing image_a are beta * (noise[, t] %*% s) / scale, where s is a binary mask from make_face_mask() over the requested signal_region and scale = sqrt(sum(s)) keeps the per-pixel signal magnitude comparable across regions of different size.

RT model

Shifted lognormal: rt = round(exp(rnorm(n, log(800), 0.5)) + 150), in ms. A small fraction of fast (<200 ms) and slow (>5000 ms) contaminants are mixed in (default 2% each) so the diagnostic functions (check_rt()) have something to flag.

Examples

if (FALSE) { # \dontrun{
# `sim$data` is a plain data frame (columns: participant_id, stimulus,
# response, condition, rt) — same shape ci_from_responses_2ifc() and
# the check_*() functions expect from your own CSV.
sim <- simulate_2ifc_data(n_per_condition = 10, n_trials = 60, seed = 1)
run_diagnostics(sim$data, method = "2ifc", col_rt = "rt")
cis <- ci_from_responses_2ifc(sim$data, rdata_path = sim$rdata_path)
run_reliability(cis$signal_matrix, n_permutations = 200L, seed = 1)
} # }