Skip to content

Discriminability Task

Try the Demo

In this task, participants view pairs of stimuli presented briefly and judge whether the two stimuli are the same or different using keyboard responses. Each trial presents two stimuli side-by-side, optionally followed by visual masks to limit perceptual processing time. The task measures how accurately and quickly participants can discriminate between stimuli.

The discriminability task implements a classic perceptual same-different paradigm that has been used extensively in vision research to measure perceptual similarity and discrimination performance1. The same-different judgment task is particularly valuable for studying perceptual spaces, as it provides direct measures of discriminability between stimulus pairs without requiring explicit category labels2. The inclusion of masking allows researchers to control perceptual difficulty and limit post-perceptual processing.

Quick Start

  1. Create or open an experiment from your Dashboard
  2. Click Add task and select "Discriminability"
  3. Upload your stimulus set (images must be square and resolution divisible by mask spatial frequency)
  4. Configure timing parameters and masking options
  5. Preview the task to verify timing and difficulty

New to Meadows? See the Getting Started guide for a complete walkthrough.

Alternative tasks

  • Use a Timed Key Response task for simpler reaction time measurements with single stimuli.
  • Use a Triplet Choice task for measuring similarity judgments without time pressure.
  • Use a Multiple Arrangement task for capturing full similarity structures across many stimuli.

Parameters

Customize the task by changing these on the Parameters tab of the task.

General Interface settings

Customize the instruction at the top of the page, as well as toolbar buttons. These apply to most task types on Meadows.

Instruction hint

Text that you can display during the task at the top of the page.

Extended instruction

A longer instruction that only appears if the participant hovers their mouse cursor over the hint.

Hint size

Whether to display the instruction, or hide it, and what font size to use.

Fullscreen button

Whether to display a button in the bottom toolbar that participants can use to switch fullscreen mode on and off.

Stimulus Display

Configure the presentation of stimulus pairs.

Item size

The size of each stimulus in the pair. Default: 8.0.

Item unit

The unit for item size. Options: degrees of visual angle, centimeters, pixels, or percentage of screen width. Default: degrees of visual angle.

Distance between streams

Distance between the center of the left and right stimuli (in units specified above). This controls how far apart the two stimuli are presented. Default: 16.0. Valid range: 1.0 to 40.0.

Trial Generation

Control how trials are generated from your stimulus set.

Maximum number of trials

The number of trials will equal the total number of unique combinations of stimuli, or this parameter, whichever is smaller. This allows you to limit very long experiments when using large stimulus sets. Default: 50. Valid range: 1 to 10,000.

Proportion of identical pairs

How many of the total number of trials should be "catch trials" with the same stimulus shown on both sides. This is essential for the same/different response to be meaningful and to calculate accurate performance metrics. Default: 0.3 (30%). Valid range: 0.0 to 0.9.

Timing

Control the temporal structure of each trial.

Stimulus duration

How long the stimuli are shown, in milliseconds. Shorter durations make the task more challenging and rely more on rapid perceptual processing. Default: 500 ms. Valid range: 1 to 5,000 ms.

Blank duration

How long to show a blank screen after the stimuli and before any masks, in milliseconds. This creates a brief interstimulus interval. Default: 30 ms. Valid range: 1 to 5,000 ms.

Response duration

How much time the participant has to respond after the stimulus presentation (and masks, if any). The fixation turns green to indicate the response window. Default: 1,000 ms. Valid range: 1 to 9,000 ms.

Pause duration

How long to wait before the next trial begins, in milliseconds. This inter-trial interval starts after the response window ends. Default: 1,000 ms. Valid range: 1 to 9,000 ms.

Self paced

If checked, the trial ends immediately after the participant responds rather than waiting for the response window to end. This can speed up the task for participants. Default: unchecked.

Masking

Configure visual masking to control perceptual difficulty.

Number of masks

How many scrambled masks to display right after the stimuli. Masks are generated by dividing stimuli into squares and scrambling them. Set to 0 to disable masking entirely. Default: 5. Valid range: 0 to 50.

Mask duration

How long each pair of masks is shown, in milliseconds. Multiple masks are presented sequentially if more than one mask is specified. Default: 500 ms. Valid range: 1 to 5,000 ms.

Mask spatial frequency

Number of rows and columns to use when generating the scrambled masks. Higher values create finer-grained scrambling. Important: The resolution of your stimulus images must be a multiple of this number, or the task will not work. For example, with 200×200 pixel images and a mask spatial frequency of 8, masks will consist of 64 squares of 25×25 pixels each. Default: 8. Valid range: 2 to 200.

Feedback

Configure performance feedback.

Show feedback

If checked, at the end of the task a summary of performance is displayed, including missed trials, average reaction time, and accuracy. Default: unchecked.

Timing of a trial

The temporal structure of each trial consists of several phases in chronological order:

  1. Pre-trial fixation (pause duration) - A gray fixation cross is displayed
  2. Stimulus presentation (stimulus duration) - Two stimuli are displayed side-by-side
  3. Blank interval (blank duration) - An empty screen, can be set to 0 to skip
  4. Masking sequence (mask duration × number of masks) - If enabled, scrambled masks are shown; set number of masks to 0 to skip this phase
  5. Response window (response duration) - The fixation turns green indicating the participant should respond with 'S' for same or 'D' for different

The actual duration on the participant's screen depends on their monitor refresh rate and may vary slightly from the specified millisecond values.

Response keys

  • S key - Judge the two stimuli as the same
  • D key - Judge the two stimuli as different

These keys are standard and not currently not configurable. Let us know if you'd like to use others.

Sampling of stimulus pairs

The pairs of stimuli shown during the task are sampled from the stimuli chosen in the Stimuli tab of the task. The actual number of trials is determined by:

  • If the total number of possible pair combinations is less than the maximum number of trials parameter, all combinations will be used
  • If the total number of possible pair combinations is greater than the maximum number of trials parameter, a random subset of pairs will be shown

The proportion of identical pairs parameter creates additional trials where the same stimulus appears on both sides. These "same" trials are critical for:

  • Making the same/different response meaningful
  • Calculating accuracy metrics
  • Preventing participants from always responding "different"

For example, with 10 stimuli and proportion of identical pairs set to 0.3: - There are 45 unique different pairs (10 choose 2) - If maximum trials is 50, you'll get approximately 35 different trials and 15 same trials

Data

For general information about the various structures and file formats that you can download for your data see Downloads.

Trial-wise "annotations" (table rows), with one row per trial. Columns:

  • trial - numerical index of the trial (starts at 0)
  • time_trial_start - timestamp when the stimulus pair was displayed (seconds since 1/1/1970)
  • time_trial_response - timestamp when the participant responded (seconds since 1/1/1970)
  • stim1_id - meadows internal id of the first stimulus
  • stim1_name - filename of the first stimulus as uploaded
  • stim2_id - meadows internal id of the second stimulus
  • stim2_name - filename of the second stimulus as uploaded
  • label - the keyboard code of the key pressed (e.g., KeyS, KeyD), or empty if no response

Important: When stim1_id equals stim2_id, the trial was a "same" trial. When they differ, it was a "different" trial.

Analysis

Calculate Reaction Times and Accuracy

Reaction time (RT) is calculated as the difference between response and start times. Accuracy requires determining the correct response based on whether the stimuli were identical.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# Load the annotations data
df = pd.read_csv('Meadows_myExperiment_v1_annotations.csv')

# Calculate reaction time in milliseconds
df['rt_ms'] = (df['time_trial_response'] - df['time_trial_start']) * 1000

# Determine trial type and correct response
df['trial_type'] = np.where(df['stim1_id'] == df['stim2_id'], 'same', 'different')
df['correct_response'] = np.where(df['trial_type'] == 'same', 'KeyS', 'KeyD')

# Calculate accuracy
df['correct'] = df['label'] == df['correct_response']

# Filter out no-response trials
df_valid = df[df['label'].notna() & (df['label'] != '')].copy()

# Overall statistics
print(f"Overall accuracy: {df_valid['correct'].mean():.1%}")
print(f"Mean RT: {df_valid['rt_ms'].mean():.0f} ms")
print(f"Median RT: {df_valid['rt_ms'].median():.0f} ms")
print(f"Response rate: {len(df_valid) / len(df) * 100:.1f}%")

# Statistics by trial type
print("\nBy trial type:")
for trial_type in ['same', 'different']:
    subset = df_valid[df_valid['trial_type'] == trial_type]
    print(f"{trial_type:>9}: {subset['correct'].mean():.1%} accurate, "
          f"{subset['rt_ms'].mean():.0f} ms RT")

# Plot RT distributions by trial type
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

for ax, trial_type in zip(axes, ['same', 'different']):
    subset = df_valid[df_valid['trial_type'] == trial_type]
    ax.hist(subset['rt_ms'], bins=30, edgecolor='black', alpha=0.7)
    ax.set_xlabel('Reaction Time (ms)')
    ax.set_ylabel('Frequency')
    ax.set_title(f'{trial_type.capitalize()} Trials')

plt.tight_layout()
plt.show()
library(tidyverse)

# Load the annotations data
df <- read_csv('Meadows_myExperiment_v1_annotations.csv')

# Calculate reaction time and accuracy
df <- df %>%
  mutate(
    rt_ms = (time_trial_response - time_trial_start) * 1000,
    trial_type = if_else(stim1_id == stim2_id, 'same', 'different'),
    correct_response = if_else(trial_type == 'same', 'KeyS', 'KeyD'),
    correct = label == correct_response
  ) %>%
  filter(!is.na(label) & label != '')

# Overall statistics
df %>%
  summarise(
    accuracy = mean(correct),
    mean_rt = mean(rt_ms),
    median_rt = median(rt_ms),
    sd_rt = sd(rt_ms),
    n_trials = n()
  )

# Statistics by trial type
df %>%
  group_by(trial_type) %>%
  summarise(
    accuracy = mean(correct),
    mean_rt = mean(rt_ms),
    median_rt = median(rt_ms),
    n = n()
  )

# Plot RT distributions by trial type and correctness
ggplot(df, aes(x = rt_ms, fill = correct)) +
  geom_histogram(bins = 30, position = 'identity', alpha = 0.6) +
  facet_wrap(~trial_type) +
  scale_fill_manual(values = c('FALSE' = 'red', 'TRUE' = 'green'),
                    labels = c('Incorrect', 'Correct')) +
  labs(x = 'Reaction Time (ms)', y = 'Frequency',
       title = 'Discriminability Task: RT Distribution',
       fill = 'Response') +
  theme_minimal()

Calculate d-prime (Signal Detection Theory)

The discriminability task is well-suited for signal detection theory analysis, where "same" trials can be treated as signal and "different" trials as noise (or vice versa).

import pandas as pd
import numpy as np
from scipy.stats import norm

# Load and prepare data
df = pd.read_csv('Meadows_myExperiment_v1_annotations.csv')
df['trial_type'] = np.where(df['stim1_id'] == df['stim2_id'], 'same', 'different')
df = df[df['label'].notna() & (df['label'] != '')].copy()

# Calculate hits and false alarms
# Treating "same" as signal: respond "same" when items are same = hit
hits = df[(df['trial_type'] == 'same') & (df['label'] == 'KeyS')]
false_alarms = df[(df['trial_type'] == 'different') & (df['label'] == 'KeyS')]

n_same = len(df[df['trial_type'] == 'same'])
n_different = len(df[df['trial_type'] == 'different'])

# Calculate rates (with correction for extreme values)
hit_rate = (len(hits) + 0.5) / (n_same + 1)
fa_rate = (len(false_alarms) + 0.5) / (n_different + 1)

# Calculate d' and criterion
d_prime = norm.ppf(hit_rate) - norm.ppf(fa_rate)
criterion = -0.5 * (norm.ppf(hit_rate) + norm.ppf(fa_rate))

print(f"Hit rate: {hit_rate:.3f}")
print(f"False alarm rate: {fa_rate:.3f}")
print(f"d' (sensitivity): {d_prime:.3f}")
print(f"c (criterion): {criterion:.3f}")

# Interpretation
if abs(criterion) < 0.2:
    bias = "unbiased"
elif criterion > 0:
    bias = "conservative (bias toward 'different')"
else:
    bias = "liberal (bias toward 'same')"

print(f"Response bias: {bias}")
library(tidyverse)

# Load and prepare data
df <- read_csv('Meadows_myExperiment_v1_annotations.csv') %>%
  mutate(
    trial_type = if_else(stim1_id == stim2_id, 'same', 'different')
  ) %>%
  filter(!is.na(label) & label != '')

# Calculate SDT measures
sdt_measures <- df %>%
  summarise(
    n_same = sum(trial_type == 'same'),
    n_different = sum(trial_type == 'different'),
    hits = sum(trial_type == 'same' & label == 'KeyS'),
    false_alarms = sum(trial_type == 'different' & label == 'KeyS')
  ) %>%
  mutate(
    # Apply correction for extreme values
    hit_rate = (hits + 0.5) / (n_same + 1),
    fa_rate = (false_alarms + 0.5) / (n_different + 1),
    d_prime = qnorm(hit_rate) - qnorm(fa_rate),
    criterion = -0.5 * (qnorm(hit_rate) + qnorm(fa_rate))
  )

print(sdt_measures %>% select(hit_rate, fa_rate, d_prime, criterion))

# Visualize response distribution
df %>%
  count(trial_type, label) %>%
  ggplot(aes(x = trial_type, y = n, fill = label)) +
  geom_col(position = 'dodge') +
  labs(x = 'Trial Type', y = 'Count', fill = 'Response',
       title = 'Response Distribution by Trial Type') +
  theme_minimal()

Technical Requirements

Stimulus constraints:

  • Images must be square (equal width and height)
  • Image resolution must be divisible by the mask spatial frequency parameter
  • Supported formats: PNG, JPG

For example, valid configurations include:

  • 200×200 pixels with mask spatial frequency of 2, 4, 5, 8, 10, 20, 25, 40, 50, 100, or 200
  • 256×256 pixels with mask spatial frequency of 2, 4, 8, 16, 32, 64, 128, or 256
  • Any square resolution works with mask spatial frequency of 1 or when masks are disabled (number of masks = 0)

References


  1. Shepard, R. N. (1987). Toward a universal law of generalization for psychological science. Science, 237(4820), 1317-1323. doi:10.1126/science.3629243 

  2. Nosofsky, R. M. (1986). Attention, similarity, and the identification-categorization relationship. Journal of Experimental Psychology: General, 115(1), 39-57. doi:10.1037/0096-3445.115.1.39