Skip to content

Wire up NARPS (ds001734) fMRI loader - #2

Merged
aaygan29 merged 5 commits into
mainfrom
narps-loader
Aug 11, 2026
Merged

Wire up NARPS (ds001734) fMRI loader#2
aaygan29 merged 5 commits into
mainfrom
narps-loader

Conversation

@aaygan29

@aaygan29 aaygan29 commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Adds the NARPS mixed-gambles loader as the framework's first fMRI real-data path, built in five small commits for a clean history. Merges on top of the DEAP loader (#1).

The dataset

NARPS (ds001734, Botvinik-Nezer et al. 2019) is a reward-circuit task: on each trial a participant sees a 50/50 gamble with a possible gain and loss and decides whether to accept. That is the anticipatory-affect paradigm the framework's ROIs come from, so it validates the fMRI loader and the NAcc/vmPFC/AIns sphere extraction on real data. Public on OpenNeuro, no licence.

Format verified against the dataset's own files (not memory): task MGT, TR 1.0 s, 4 runs × 64 trials, events.tsv columns onset, duration, gain, loss, RT, participant_response, responses strongly/weakly_accept, strongly/weakly_reject, NoResp; participants.tsv group column (equalIndifference / equalRange).

The honest framing

NARPS is an individual-level fMRI validation, and the PR is careful not to oversell it. On gambles, acceptance is largely a function of gain/loss through expected value, so the economic baseline forecasts aggregate acceptance almost by construction. So:

  • Individual arm (primary): predict accept/reject from ROI betas. On the fixture, fMRI reaches 0.80 balanced accuracy, subject-grouped CV.
  • Aggregate arm (secondary, behaviour-dominated by design): forecast population acceptance rate per gamble. On the fixture, behaviour (R² 0.75) and brain (0.71) run close, behaviour ahead — exactly as docs/narps.md predicts.

No brain-beats-behaviour headline is asserted anywhere, because gambles are the wrong task for it. That would misrepresent the data.

Commits (incremental)

  1. Docsdocs/narps.md + data_sources.md, before any code.
  2. Core loader — events parsing, response binarisation, NoResp drop, gamble keys, behaviour block, aggregate acceptance rate. No neuroimaging dependency.
  3. BIDS load pathNARPSLoader.load discovers runs and delegates BOLD→ROI extraction to FMRILoader; adds optional per-run confound regression to FMRILoader.load.
  4. Fixture + tests — a miniature but format-real BIDS tree (real events.tsv, participants.tsv, tiny 4-D NIfTIs with an affine placing all five ROIs in-volume, confounds), with an EV-scaled bump planted at NAcc/MPFC. 22 tests.
  5. Orchestration + demo + CIload_narpsMultimodalDataset, group filtering, scripts/run_narps.py --demo, CI wired to install .[fmri] so the BIDS path runs rather than skips.

Validation

  • 119 tests passing (up from 97). Core NARPS tests run everywhere; the nilearn/nibabel BIDS tests run in CI now that .[fmri] is installed.
  • The planted NAcc↔EV signal is recovered through real nilearn sphere extraction (r > 0.5), and run_narps.py --demo passes its recovery checks (fMRI predicts accept above chance, survives permutation).

Still needs

The OpenNeuro download to run for real, ideally with fMRIPrep derivatives. The peak-window ROI mean is a stand-in for a first-level GLM (noted in provenance and docs); swap in nilearn.glm.first_level before reporting.

Aayush Gandhi added 5 commits August 7, 2026 17:12
Verified the format against the dataset's own files on OpenNeuro rather than
memory: task MGT, TR 1.0 s, 4 runs x 64 trials, events.tsv columns
(onset, duration, gain, loss, RT, participant_response) with responses
strongly/weakly_accept, strongly/weakly_reject, NoResp; participants.tsv group
column (equalIndifference / equalRange).

docs/narps.md records the mapping onto the framework (accept/reject as the
individual outcome, the (gain,loss) gamble as the stimulus key, population
acceptance rate as the aggregate), the traps, and an honest statement of NARPS's
role. Key point: on gambles the economic baseline dominates the aggregate arm by
construction, so NARPS is an individual-level fMRI plumbing check, NOT a
brain-beats-behaviour demonstration. Forcing that headline would misrepresent
the task.
The neuroimaging-free layer of the NARPS loader: parses events.tsv, binarises
participant_response (strongly/weakly_accept -> 1, *_reject -> 0), drops NoResp
trials (RT=0 is missing data, not a fast response), keys each gamble by
(gain, loss) as e.g. g14_l06, and builds the behaviour block from the economic
variables (gain, loss, expected value, |EV|, RT).

Also: acceptance_rate_by_gamble computes the population acceptance rate per
gamble (the NARPS aggregate outcome), excluding gambles seen by too few
subjects. from_events_and_rois assembles fMRI + behaviour blocks from a
precomputed trial-by-ROI matrix, reusing FMRILoader; this is the entry point the
nilearn .load path will call after extraction.

Verified against a real ds001734 events file. No neuroimaging dependency in this
layer.
NARPSLoader.load discovers a subject's runs by globbing *_task-MGT_*_events.tsv,
matches each to its preprocessed BOLD and confounds by run label (refusing to
proceed on a mismatch, since a dropped run misaligns trials against BOLD), then
delegates the BOLD -> ROI sphere extraction to FMRILoader and concatenates runs
into one fMRI block plus the behaviour block.

FMRILoader.load gains an optional per-run `confounds` argument passed to the
NiftiSpheresMasker, so motion and physiological regressors are removed from the
ROI timeseries. It warns when confounds are omitted rather than treating that as
fine: for task fMRI, head motion correlates with events. Recorded in provenance
as confounds_regressed.

Requires nilearn; exercised by the fixture in the next commit.
tests/narps_fixture.py writes a miniature but format-real NARPS BIDS tree:
participants.tsv with a group column, per-run events.tsv with the verified
columns and response strings, a small 4-D preprocessed BOLD NIfTI with an
affine placing all five default ROIs inside the volume, and a confounds table.
An EV-scaled BOLD bump is planted at the NAcc and MPFC voxels so extraction can
be checked; consistent with docs/narps.md, no brain-beats-behaviour claim is
planted.

tests/test_narps.py: 19 tests. The core (parsing, binarisation, NoResp drop,
keys, EV, behaviour block, aggregate acceptance rate, format errors) runs
everywhere; the BIDS .load tests importorskip nilearn/nibabel and validate real
sphere extraction locally, including that the planted NAcc-EV signal is
recovered (r>0.5) and that confounds are regressed. Skipped cleanly in a
minimal CI env.
load_narps assembles per-subject blocks into an individual-level
MultimodalDataset: one row per trial, y_individual is accept vs reject, CV
groups by subject. It is built by direct construction rather than the
(subject, stimulus) join, because a subject sees each gamble more than once and
that join requires unique keys; the fMRI and behaviour blocks are row-aligned by
shared event ordering, which is what MultimodalDataset actually needs. Keeping
the gamble as the stimulus key also makes the aggregate arm (population
acceptance rate per gamble) work.

Group filtering is supported and warns when pooling equalIndifference and
equalRange for the aggregate arm, since they saw different gamble matrices.

scripts/run_narps.py --demo runs the whole path on the synthetic BIDS fixture
and checks the honest, recoverable claim: fMRI ROI features predict accept above
chance out of fold and survive label permutation. It does NOT assert
brain-beats-behaviour, because on gambles the economic baseline dominates.

On the fixture: fMRI balanced accuracy 0.80 individually; on the aggregate arm
behaviour (0.75) and brain (0.71) are close, behaviour ahead, exactly as
docs/narps.md predicts.

CI now installs .[dev,fmri] so the NARPS BIDS tests and demo run rather than
skip. 119 tests passing.
@aaygan29
aaygan29 merged commit be5b812 into main Aug 11, 2026
2 checks passed
@aaygan29
aaygan29 deleted the narps-loader branch August 11, 2026 11:35
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