A lead researcher explaining the benefits of Cognition.run

Psychophysics and Online Experiments with Cognition

What psychophysics is

Psychophysics is the quantitative study of the relationship between physical stimuli and the sensations they produce. The name and the programme come from Gustav Fechner, whose Elemente der Psychophysik (1860) proposed that sensation could be measured indirectly by finding the smallest stimulus differences a person can detect. Fechner built on Ernst Weber's observation that the just-noticeable difference (JND) between two stimuli is roughly a constant fraction of their magnitude (Weber's law, ΔI / I = k). From it he derived Fechner's law, which states that sensation grows with the logarithm of stimulus intensity.

A century later, S. S. Stevens (1957) argued that people can directly rate sensation magnitude, and that the resulting data fit a power function, S = k·Ia, rather than a logarithm. The exponent a differs by modality: below 1 for brightness and loudness (compressive), close to 1 for visual length, above 1 for electric shock (expansive). Weber, Fechner and Stevens remain the starting point of any psychophysics course, and the methods they inspired are still in use today.

Classical methods

The goal of most psychophysical procedures is to estimate a threshold: the stimulus level at which a participant detects the stimulus (absolute threshold) or tells two stimuli apart (difference threshold) with a given probability, typically 50–75 % correct.

  • Method of limits. The stimulus is presented in ascending or descending series until the response changes. Fast, but prone to habituation and expectation errors, which is why ascending and descending runs are alternated and averaged.
  • Method of adjustment. The participant controls the stimulus (a slider for contrast, a dial for loudness) and sets it to the point where it is just detectable or matches a standard. Very fast and intuitive, but the least precise of the classical methods.
  • Method of constant stimuli. A fixed set of stimulus levels is presented many times in random order. The proportion of "yes" or "correct" responses at each level gives a psychometric function, to which a cumulative Gaussian or logistic is fitted. Most accurate, but requires many trials, many of them far from threshold.
  • Adaptive (staircase) methods. Each trial's intensity depends on previous responses, so testing concentrates near threshold. The transformed up-down rules of Levitt (1971) are the workhorse: a 1-up-2-down staircase lowers intensity after two consecutive correct responses and raises it after one error, converging on the 70.7 % correct point. Bayesian methods such as QUEST (Watson & Pelli, 1983) keep a posterior over threshold and place each trial where it is most informative, reaching a stable estimate in a few dozen trials.

What a browser can and cannot measure well

Laboratory psychophysics relies on calibrated hardware. A web browser on a participant's own device removes most of that control, so it is worth being explicit about what transfers and what does not.

  • Presentation timing. Browsers repaint at the display's refresh rate (usually 60 Hz, i.e. 16.7 ms per frame). jsPsych schedules stimulus offsets with requestAnimationFrame, so durations are quantised to frames and a "50 ms" stimulus will actually last 3 or 4 frames. Paradigms that need single-frame precision, such as very brief masked presentations, are risky online; anything at 100 ms or longer is generally fine.
  • Luminance and contrast. You cannot know the gamma curve, brightness setting or ambient light of a participant's screen. Absolute contrast thresholds are therefore not comparable across participants. Relative comparisons within a participant (condition A vs. condition B in the same session) remain valid, and that is how most online psychophysics is designed.
  • Viewing distance and stimulus size. Without knowing pixel density and distance, you cannot express stimuli in degrees of visual angle. The jsPsychVirtualChinrest plugin (Li, Joo, Yeatman & Binda, 2020) solves a large part of this: participants resize an on-screen rectangle to match a credit card, which gives pixels per millimetre, and then fixate a point while a dot moves until it disappears into their blind spot, which gives viewing distance. The plugin reports both values so you can scale stimuli in degrees for the rest of the session.
  • Response timing. Keyboard reaction times carry a few milliseconds of variability from the operating system and browser, but within-subject effects of tens of milliseconds are reliably recovered online.
  • Colour and audio. Colour-critical work (isoluminance, cone-isolating stimuli) is not feasible. Auditory thresholds in absolute terms are not either, but frequency or duration discrimination relative to a reference works if you ask participants to use headphones and include a simple headphone check.

Example: a 1-up-2-down contrast staircase in jsPsych 8

The snippet below presents a grey disc on a grey background and asks whether it appeared on the left or the right. Contrast goes down after two correct answers in a row and up after one error. The loop stops after eight reversals, and the threshold is the mean contrast at the last six reversals.

const jsPsych = initJsPsych();

let contrast = 0.5;     // starting Michelson contrast (0..1)
let step = 0.05;
let correctStreak = 0;
let lastDirection = null;
let reversals = [];

const stim = (side, c) => {
  const lum = Math.min(255, Math.round(128 + 127 * c));
  const disc = `<div style="width:60px;height:60px;border-radius:50%;
    background:rgb(${lum},${lum},${lum});display:inline-block"></div>`;
  const blank = `<div style="width:60px;height:60px;display:inline-block"></div>`;
  return `<div style="background:rgb(128,128,128);padding:80px">
    ${side === "left" ? disc + blank : blank + disc}</div>`;
};

const trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: () => stim(jsPsych.evaluateTimelineVariable("side"), contrast),
  choices: ["f", "j"],
  stimulus_duration: 200,
  trial_duration: 2000,
  data: { task: "staircase", side: jsPsych.timelineVariable("side") },
  on_start: (t) => { t.data.contrast = contrast; },
  on_finish: (data) => {
    const expected = data.side === "left" ? "f" : "j";
    data.correct = jsPsych.pluginAPI.compareKeys(data.response, expected);
    let direction = null;
    if (data.correct) {
      correctStreak++;
      if (correctStreak === 2) { contrast -= step; correctStreak = 0; direction = "down"; }
    } else {
      contrast += step; correctStreak = 0; direction = "up";
    }
    if (direction && lastDirection && direction !== lastDirection) reversals.push(data.contrast);
    if (direction) lastDirection = direction;
    contrast = Math.min(1, Math.max(0.01, contrast));
  }
};

const staircase = {
  timeline: [trial],
  timeline_variables: [{ side: "left" }, { side: "right" }],
  sample: { type: "with-replacement", size: 1 },
  loop_function: () => reversals.length < 8
};

const done = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: () => {
    const last = reversals.slice(-6);
    const thr = last.reduce((a, b) => a + b, 0) / last.length;
    jsPsych.data.addProperties({ threshold: thr.toFixed(3) });
    return `<p>Done. Estimated threshold: ${thr.toFixed(3)}</p>`;
  }
};

jsPsych.run([staircase, done]);

For a real study you would add a fixation cross, practice trials, a jsPsychFullscreen trial and a jsPsychVirtualChinrest trial at the start, and you would probably step contrast in log units rather than linearly. If you need a faster-converging estimate, the QUEST algorithm can be implemented in plain JavaScript and plugged into the same on_finish structure.

Hosting it on Cognition

Paste the code into the Cognition editor, select jsPsych 8.2.3, and use the live preview to run the staircase yourself while watching the console for errors. Upload any images you need as stimuli, then share the generated https://<token>.cognition.run link with participants. Every trial, including the contrast and correct columns added above, is sent to the server in real time, and a service worker buffers data locally if a participant's connection drops. When the study ends you download a CSV or JSON per participant and fit psychometric functions in R or Python, or use the built-in analyzer for quick comparisons between conditions.

Create a free account to host up to four tasks with sixty participants each, or read the documentation for details on stimuli, consent forms and data export.