Skip to content

Writing HED into events.json: practical BIDS examples

Why HED usually belongs in events.json

In BIDS, the most maintainable way to annotate events is usually to place the HED information in the JSON sidecar rather than writing a full custom HED string for every single row.

This approach is easier to:

  • read
  • maintain
  • validate
  • reuse across many event rows

In most datasets, the event rows in events.tsv contain values such as event type, condition, stimulus file, and response label. The events.json file explains what those values mean and attaches HED annotations to them.


Example 1: a simple fixation -> face stimulus -> button press experiment

This is a beginner-friendly example similar to many EEG, MEG, and behavioral tasks.

events.tsv

onset   duration    trial_type  face_type   response    stim_file
1.2 0.5 fixation    n/a n/a cross.png
2.0 0.5 show_face   unfamiliar_face n/a faces/u032.bmp
2.7 n/a left_press  n/a correct 

What the columns mean

Column Meaning
trial_type Main event category recorded in this row
face_type Whether the shown face is familiar or unfamiliar
response Behavioral outcome
stim_file Path or file name of the visual stimulus

events.json

{
  "trial_type": {
    "Description": "Primary event type for each row",
    "Levels": {
      "fixation": "Fixation cross is displayed",
      "show_face": "A face image is shown",
      "left_press": "Participant presses the left response button"
    },
    "HED": {
      "fixation": "Sensory-event, Visual-presentation, Instructional, (Cross, White-color)",
      "show_face": "Sensory-event, Visual-presentation, Experimental-stimulus, (Image, Face)",
      "left_press": "Agent-action, Press, (Experiment-participant, Human-agent), Correct-action"
    }
  },
  "face_type": {
    "Description": "Type of face shown on face trials",
    "Levels": {
      "unfamiliar_face": "The face is unfamiliar to the participant"
    },
    "HED": {
      "unfamiliar_face": "(Condition-variable/Face-identity, Label/Unfamiliar-face)"
    }
  },
  "response": {
    "Description": "Behavioral classification of the participant response",
    "Levels": {
      "correct": "The response is correct"
    },
    "HED": {
      "correct": "Correct-action"
    }
  },
  "stim_file": {
    "Description": "Path to the stimulus file",
    "HED": "(Image, Pathname/#)"
  }
}

What this achieves

For the row with show_face, the assembled annotation combines:

  • the main event type from trial_type
  • the condition detail from face_type
  • the file path from stim_file

That gives a much richer event description than the row alone.


Example 2: adding a cover story or task context

Many experiments do not present stimuli in isolation. They are embedded in a task story, block context, or instructional context.

HED can capture that context in a reusable way.

Scenario

Participants are told that they are working as an airport security screener. They see baggage images and must detect whether a target item is present.

This is a classic example where the cover story matters scientifically, because it shapes attention, expectations, and decision strategy.

events.tsv

onset   duration    event_type  item_status response    stim_file
0.0 2.0 instruction_screen  n/a n/a instructions01.png
2.5 0.8 show_bag    no_target   n/a bags/bag001.png
3.4 n/a button_press    n/a hit 
4.2 0.8 show_bag    target_present  n/a bags/bag002.png
5.0 n/a button_press    n/a miss    

events.json

{
  "event_type": {
    "Description": "Main event type",
    "Levels": {
      "instruction_screen": "Instruction screen is shown",
      "show_bag": "Baggage image is shown",
      "button_press": "Participant button response"
    },
    "HED": {
      "instruction_screen": "Sensory-event, Visual-presentation, Instructional, (Image, Text)",
      "show_bag": "Sensory-event, Visual-presentation, Experimental-stimulus, (Image, Bag)",
      "button_press": "Agent-action, Press, (Experiment-participant, Human-agent)"
    }
  },
  "item_status": {
    "Description": "Whether the bag contains the target item",
    "Levels": {
      "no_target": "No target object is present",
      "target_present": "A target object is present"
    },
    "HED": {
      "no_target": "Distractor",
      "target_present": "Target"
    }
  },
  "response": {
    "Description": "Behavioral response category",
    "Levels": {
      "hit": "Participant correctly detects a target",
      "miss": "Participant fails to report a target"
    },
    "HED": {
      "hit": "Correct-action",
      "miss": "Incorrect-action"
    }
  },
  "stim_file": {
    "Description": "Stimulus file path",
    "HED": "(Image, Pathname/#)"
  }
}

How to represent the cover story more explicitly

If the cover story is central to the experiment, you can represent it using a reusable definition.

Example HED definition

(Definition/Airport-security-task, (Task, Label/Airport-security-screening))

You can then include that concept where needed:

Def/Airport-security-task

For example, the show_bag entry could be extended conceptually to include that the stimulus belongs to the airport security task context.

This is especially useful when the same context applies across many rows.


Example 3: oddball EEG experiment

This type of example is highly relevant for neuroscientists.

events.tsv

onset   duration    event_type  stimulus_type   response    reaction_time
0.5 0.1 play_tone   standard    n/a n/a
1.7 0.1 play_tone   target  n/a n/a
2.1 n/a button_press    n/a correct 0.42
2.9 0.1 play_tone   standard    n/a n/a

events.json

{
  "event_type": {
    "Description": "Main type of event",
    "Levels": {
      "play_tone": "An auditory tone is presented",
      "button_press": "Participant presses the response button"
    },
    "HED": {
      "play_tone": "Sensory-event, Auditory-presentation, Experimental-stimulus, Tone",
      "button_press": "Agent-action, Press, (Experiment-participant, Human-agent)"
    }
  },
  "stimulus_type": {
    "Description": "Role of the tone in the oddball task",
    "Levels": {
      "standard": "Frequent non-target tone",
      "target": "Rare target tone"
    },
    "HED": {
      "standard": "Distractor",
      "target": "Target"
    }
  },
  "response": {
    "Description": "Outcome of the participant response",
    "Levels": {
      "correct": "Correct target response"
    },
    "HED": {
      "correct": "Correct-action"
    }
  },
  "reaction_time": {
    "Description": "Reaction time in seconds",
    "Units": "s",
    "HED": "(Agent-action, Label/Reaction-time, Duration/# s)"
  }
}

Why this works well for EEG

This structure clearly separates:

  • the sensory event (play_tone)
  • the task role (standard vs target)
  • the participant response (button_press)
  • the behavioral outcome (correct)
  • the value column (reaction_time)

This makes the dataset much easier to search and analyze later.


Example 4: trial and block structure with reusable definitions

In many experiments, not every event is a stimulus or response. Some events define structure.

Examples:

  • block start
  • trial start
  • rest period onset
  • stimulus sequence onset and offset

HED is very good at representing this structure.

Conceptual example definition set

(Definition/Task-block, (Time-block, Task))
(Definition/Trial-context, (Experimental-trial, Task))

Possible event rows

onset   duration    event_type  block_type
0.0 30  block_start face_block
30.0    2   trial_start n/a
32.0    0.5 show_face   n/a
33.0    n/a button_press    n/a

Possible sidecar idea

{
  "event_type": {
    "HED": {
      "block_start": "Experiment-structure, Def/Task-block",
      "trial_start": "Experiment-structure, Def/Trial-context",
      "show_face": "Sensory-event, Visual-presentation, Experimental-stimulus, (Image, Face)",
      "button_press": "Agent-action, Press, (Experiment-participant, Human-agent)"
    }
  },
  "block_type": {
    "HED": {
      "face_block": "Label/Face-block"
    }
  }
}

This is a simplified pattern, but it shows how HED can annotate not only isolated stimuli but also the structure of the experiment.


How to think when writing events.json

A practical workflow for researchers is:

Step 1: identify the main columns

Usually these are columns like:

  • trial_type
  • event_type
  • stim_type
  • response
  • condition
  • stim_file

Step 2: decide whether a column is categorical or value-based

Column type Typical examples How to annotate
Categorical show_face, left_press, target, incorrect Map each value to a HED string
Value-based file path, RT, numeric ID, intensity value Use a HED string with #

Step 3: annotate meaning, not just code labels

Bad thinking:

code 13 means something important to my lab

Better thinking:

this row marks the onset of a visual face stimulus that serves as a target

Step 4: keep reusable meaning in the sidecar

If the same concept appears many times, put it in the sidecar once instead of rewriting it row by row.


Beginner mistakes to avoid in events.json

Mistake Why it is a problem Better approach
Writing everything only in events.tsv Hard to maintain, hard to reuse Put shared meaning in events.json
Annotating only codes Other users cannot understand the experiment Annotate scientific meaning
Mixing categorical and value logic Validation becomes confusing Separate category maps from # value placeholders
Using overcomplicated tags too early Makes documentation harder to read Start simple, then refine
Ignoring task context Loses important experimental meaning Add context through properties or definitions

A compact template you can adapt

{
  "event_type": {
    "Description": "Main event type",
    "Levels": {
      "stimulus": "A stimulus is shown",
      "response": "Participant response"
    },
    "HED": {
      "stimulus": "Sensory-event, Visual-presentation, Experimental-stimulus, (Image, Face)",
      "response": "Agent-action, Press, (Experiment-participant, Human-agent)"
    }
  },
  "condition": {
    "Description": "Experimental condition",
    "Levels": {
      "target": "Target condition",
      "distractor": "Distractor condition"
    },
    "HED": {
      "target": "Target",
      "distractor": "Distractor"
    }
  },
  "stim_file": {
    "Description": "Stimulus file",
    "HED": "(Image, Pathname/#)"
  }
}

Key takeaway

The goal of events.json is not just to satisfy BIDS metadata requirements. It is to turn event rows into scientifically meaningful, machine-actionable annotations.

For beginners, the most useful pattern is:

  • annotate the main event column first
  • add condition and response meaning next
  • use # for row-wise values such as file names or reaction times
  • use definitions when complex concepts repeat

That approach is usually enough to make a BIDS dataset far easier to understand and reuse.