Skip to content

Compound stimulus - #16

Merged
alexevag merged 2 commits into
mainfrom
compound-stimulus
Sep 7, 2026
Merged

Compound stimulus#16
alexevag merged 2 commits into
mainfrom
compound-stimulus

Conversation

@alexevag

@alexevag alexevag commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Description

Compound (multimodal) stimuli were exported based on their class name: the exporter split stimulus_class on underscores and treated each piece as a component table. That only worked for classes named after their tables, and a freely named class such as TonesGrating exported nothing usable.

Components are now resolved from the data instead. For a session, the exporter looks up which stimulus tables actually hold conditions for the trials of that stimulus class, so any naming scheme works and cond_tables is the single source of truth.

Two related export bugs are fixed along the way:

  • Every component is now written to the NWB file. Previously the conditions module received only the first component, so a compound stimulus lost the parameters of all the others. Components are written as Stimulus_<Component>, and a simple stimulus keeps the single Stimulus table.
  • Condition rows are deduplicated on the primary key rather than the condition hash. Part tables that extend the key (Panda.Object adds obj_id, Panda.Light adds light_idx) produce several rows per hash, and collapsing on the hash kept only one of them. The repeated presentation timing rows are collapsed instead.

Docs: a new guide on building a compound stimulus, plus an update to the NWB export docs covering component resolution and stimuli with part tables.

How Has This Been Tested?

Exported sessions with export_to_nwb(animal_id=..., session_id=...) and inspected
the resulting file:

  • a session with a compound stimulus, checking that every component appears undernwbfile.stimulus and as a Stimulus_<Component> table in the Conditions module
  • a session with a simple stimulus, checking the single Stimulus table is unchanged
  • a session using a stimulus with part tables, checking condition rows are kept per (object, light) combination while presentation timing stays one row per trial

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings

alexevag and others added 2 commits September 2, 2026 17:22
The exporter decided a stimulus was compound by looking for an
underscore in the class name. Plugin classes are named TonesGrating and
TonesPanda, so they took the single-stimulus branch and only the first
modality reached the file. No error was raised: the result was a valid
NWB file missing the other modality's parameters.

Components are now read back from the database instead of parsed out of
the name. A stimulus writes one row per cond_table under the trial's
stim_hash, so the components are the stimulus tables holding conditions
for that session's trials. This drops the naming requirement and works
for both naming styles, while leaving single stimuli whose names contain
words that look like table names (PsychoGrating, VROdors) intact.

Three further defects surfaced once the compound path ran:

- Only the first component's conditions were written. Each component now
  gets its own Conditions/Stimulus_<Component> table; a simple stimulus
  keeps the single Stimulus table.
- Conditions were deduplicated on the hash alone, which discarded the
  rows of part tables that extend the primary key. Panda.Object adds
  obj_id and Panda.Light adds light_idx, so a two-object two-light
  condition kept 1 of its 4 combinations. Dedup is now on the primary
  key of the joined table.
- Those same part tables repeated each presentation once per
  combination, giving 16 identical timing rows for 4 trials. The
  presentation timing is collapsed back to one row per trial.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S7gfyfdvHFBvXP5RNMPJ5c
Explains how to combine several modalities into one stimulus class,
which is done by inheritance rather than composition: subclass the
dominant modality, then extend cond_tables, required_fields and
default_key. Includes a minimal Tones + Grating example and the task
file that runs it.

The bulk of the guide is the pitfalls, most of which fail silently
rather than raising. Stimulus.__init__ wipes cond_tables,
required_fields and default_key, so a subclass that sets them as class
attributes gets an empty contract and every condition collapses to the
same stim_hash. A missing required field skips a whole condition table.
A component decorated with the stimulus schema registers a second time.
Logging a trial in both parents duplicates it.

Also distinguishes a compound stimulus, where the modalities share one
stim_hash, from stim_periods, which sequences them within a trial.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S7gfyfdvHFBvXP5RNMPJ5c
@alexevag
alexevag merged commit f546b6d into main Sep 7, 2026
1 check passed
@alexevag
alexevag deleted the compound-stimulus branch September 10, 2026 08:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant