Organize, validate, and query neuroimaging datasets in BIDS 1.8 format using pybids, mne-bids, and datalad; covers EEG, fMRI, and derivatives conventions.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Instruções da origem · Visualização somente leitura
name
bids-neuroimaging
description
Organize, validate, and query neuroimaging datasets in BIDS 1.8 format using pybids, mne-bids, and datalad; covers EEG, fMRI, and derivatives conventions.
BIDS Neuroimaging: Organizing and Querying Brain Data
One-line summary: This Skill helps researchers convert raw neuroimaging data to BIDS 1.8
format, validate the structure, query datasets with pybids, and share via OpenNeuro/datalad.
When to Use This Skill
Use this Skill in the following scenarios:
When you need to organize EEG, MEG, or fMRI data into a standards-compliant BIDS directory
When your data has a multi-subject, multi-session structure requiring consistent naming
When you need to query a BIDS dataset to filter by task, subject, run, or modality
When you are using OpenNeuro to download or share public neuroimaging datasets
When you need to produce events.tsv files and sidecar JSON metadata from experimental logs
When preprocessing outputs must conform to BIDS derivatives conventions
participant_id: must match sub-<label> directory names exactly
age, sex: BIDS-recommended demographic columns
All paths use forward slashes and lowercase entity keys
Step 2: Convert EEG Data with mne-bids
mne_bids.write_raw_bids handles file conversion, channel coordinate writing, and sidecar JSON
generation from an MNE Raw object and a BIDSPath descriptor.
import mne
import mne_bids
import numpy as np
import pandas as pd
from pathlib import Path
BIDS_ROOT = Path("my_bids_dataset")
# --- Simulate a raw EEG file (replace with mne.io.read_raw_edf / read_raw_bdf) ---
n_channels = 32
sfreq = 250.0
duration_s = 120.0
n_samples = int(sfreq * duration_s)
rng = np.random.default_rng(seed=42)
data = rng.normal(scale=5e-6, size=(n_channels, n_samples)) # ~5 µV noise
ch_names = [
"Fp1", "Fp2", "F7", "F3", "Fz", "F4", "F8",
"T7", "C3", "Cz", "C4", "T8",
"P7", "P3", "Pz", "P4", "P8",
"O1", "Oz", "O2",
"FC1", "FC2", "CP1", "CP2",
"AF3", "AF4", "FC5", "FC6", "CP5", "CP6",
"EOGh", "EOGv",
]
ch_types = ["eeg"] * 30 + ["eog", "eog"]
info = mne.create_info(ch_names=ch_names, sfreq=sfreq, ch_types=ch_types)
info["line_freq"] = 50.0# European power line
raw = mne.io.RawArray(data, info)
montage = mne.channels.make_standard_montage("standard_1020")
raw.set_montage(montage, on_missing="ignore")
# --- Add stimulus annotations (simulated events at 250, 750, 1250 ... ms) ---
event_onsets = np.arange(1.0, 100.0, 2.5) # Every 2.5 s
event_durations = np.zeros(len(event_onsets))
# Alternate standard (80%) and deviant (20%)
event_descriptions = [
"deviant"if i % 5 == 4else"standard"for i inrange(len(event_onsets))
]
annotations = mne.Annotations(
onset=event_onsets,
duration=event_durations,
description=event_descriptions,
)
raw.set_annotations(annotations)
# --- Define BIDSPath ---
bids_path = mne_bids.BIDSPath(
subject="01",
session="01",
task="oddball",
run="1",
datatype="eeg",
root=BIDS_ROOT,
)
# --- Write to BIDS (creates sidecar JSON, channels.tsv, events.tsv) ---
mne_bids.write_raw_bids(
raw,
bids_path=bids_path,
overwrite=True,
verbose=False,
format="EDF", # Save as EDF; use "auto" to keep original format
events=None, # Derived from annotations automatically
event_id={"standard": 1, "deviant": 2},
)
print(f"BIDS EEG written to: {bids_path.fpath}")
# Inspect generated sidecar
sidecar_path = bids_path.copy().update(suffix="eeg", extension=".json").fpath
import json
withopen(sidecar_path) as f:
sidecar = json.load(f)
print("EEG sidecar JSON keys:", list(sidecar.keys()))
Parameter reference:
Parameter
Meaning
Recommended value
Notes
format
Output file format
"EDF" or "auto"
"auto" preserves original
overwrite
Overwrite existing files
True during development
Set to False for production
event_id
Annotation-to-code mapping
Dict matching annotation descriptions
Used to build events.tsv
verbose
Verbosity level
False or "WARNING"
MNE log level string or bool
Step 3: Query the BIDS Dataset with pybids BIDSLayout
BIDSLayout provides a Pythonic API to discover and filter files in a BIDS dataset.
from bids import BIDSLayout
import pandas as pd
from pathlib import Path
BIDS_ROOT = Path("my_bids_dataset")
# --- Initialize layout (indexes all files on first call) ---------------------
layout = BIDSLayout(str(BIDS_ROOT), validate=False)
# --- High-level queries -------------------------------------------------------
subjects = layout.get_subjects()
tasks = layout.get_tasks()
sessions = layout.get_sessions()
print(f"Subjects: {subjects}")
print(f"Tasks: {tasks}")
print(f"Sessions: {sessions}")
# --- Filter files by entity --------------------------------------------------# Get all EEG data files for sub-01
eeg_files = layout.get(
subject="01",
datatype="eeg",
suffix="eeg",
extension=[".edf", ".bdf", ".fif", ".set"],
)
for f in eeg_files:
print(f" {f.path}")
# --- Access the events.tsv for a specific file --------------------------------
events_files = layout.get(
subject="01",
task="oddball",
suffix="events",
extension=".tsv",
)
for ef in events_files:
events_df = pd.read_csv(ef.path, sep="\t")
print(f"\nevents.tsv — {ef.path}")
print(events_df.head())
# --- Build a summary DataFrame of all EEG files ------------------------------
all_eeg = layout.get(datatype="eeg", suffix="eeg", extension=".edf")
summary_records = []
for f in all_eeg:
summary_records.append(
{
"subject": f.entities.get("subject"),
"session": f.entities.get("session"),
"task": f.entities.get("task"),
"run": f.entities.get("run"),
"path": f.path,
}
)
summary_df = pd.DataFrame(summary_records)
print("\nDataset summary:")
print(summary_df.to_string())
Step 4: Validate with bids-validator
# Validate the entire dataset (requires Node.js bids-validator)
bids-validator my_bids_dataset --verbose
# Non-interactive mode (suitable for CI pipelines)
bids-validator my_bids_dataset --json 2>/dev/null | python -c "
import sys, json
report = json.load(sys.stdin)
errors = report.get('issues', {}).get('errors', [])
warnings = report.get('issues', {}).get('warnings', [])
print(f'Errors: {len(errors)}, Warnings: {len(warnings)}')
for e in errors[:5]:
print(' ERROR:', e.get('key'), '-', e.get('reason'))
"
Interpreting results:
Errors: must be fixed before the dataset can be published to OpenNeuro
Warnings: recommended to fix but do not block submission
Common errors: missing IntendedFor in fieldmap sidecar, wrong entity order in filename
Advanced Usage
Derivatives Folder Conventions
BIDS derivatives store preprocessed data under derivatives/<pipeline-name>/, maintaining the
same subject/session/datatype hierarchy and appending a desc-<label> entity.
# Install datalad and the git-annex backend
pip install datalad
# On Linux: sudo apt install git-annex# On macOS: brew install git-annex# List available datasets (browse openneuro.org for ds-numbers)# Install (clone metadata only; no large files downloaded yet)
datalad install https://github.com/OpenNeuroDatasets/ds003490.git
cd ds003490
# Download only sub-01 data
datalad get sub-01/
# Download the entire dataset (may be large)
datalad get .
import subprocess
from pathlib import Path
defdatalad_get_subject(dataset_url: str, subject_id: str, local_dir: str) -> bool:
"""
Clone an OpenNeuro dataset and download a single subject's files.
Parameters
----------
dataset_url : str
GitHub URL of the OpenNeuro dataset, e.g.
'https://github.com/OpenNeuroDatasets/ds003490.git'
subject_id : str
Subject label without 'sub-' prefix, e.g. '01'
local_dir : str
Local path to clone into.
Returns
-------
bool : True if all commands succeeded.
"""
local_path = Path(local_dir)
ifnot local_path.exists():
result = subprocess.run(
["datalad", "install", dataset_url, str(local_path)],
capture_output=True, text=True,
)
if result.returncode != 0:
print(f"datalad install failed: {result.stderr}")
returnFalseprint(f"Installed dataset to {local_path}")
subject_dir = local_path / f"sub-{subject_id}"
result = subprocess.run(
["datalad", "get", str(subject_dir)],
capture_output=True, text=True, cwd=str(local_path),
)
if result.returncode != 0:
print(f"datalad get failed: {result.stderr}")
returnFalseprint(f"Downloaded sub-{subject_id} to {subject_dir}")
returnTrue# Example (replace with a real dataset URL)# datalad_get_subject(# "https://github.com/OpenNeuroDatasets/ds003490.git",# subject_id="01",# local_dir="ds003490_local",# )
Building events.tsv from a Log File
import pandas as pd
import numpy as np
from pathlib import Path
deflog_to_events_tsv(
log_csv: str,
onset_col: str,
trial_type_col: str,
duration_col: str | None = None,
extra_cols: list[str] | None = None,
) -> pd.DataFrame:
"""
Convert a stimulus presentation log CSV to BIDS events.tsv format.
Parameters
----------
log_csv : str
Path to the experiment log file.
onset_col : str
Column containing event onset times in seconds.
trial_type_col : str
Column containing event type labels.
duration_col : str, optional
Column for event durations. If None, duration is set to 0.
extra_cols : list of str, optional
Additional columns to include (e.g., 'response_time', 'accuracy').
Returns
-------
pd.DataFrame with BIDS-compliant column order.
"""
log_df = pd.read_csv(log_csv)
events = pd.DataFrame()
events["onset"] = log_df[onset_col].astype(float)
events["duration"] = (
log_df[duration_col].astype(float) if duration_col else0.0
)
events["trial_type"] = log_df[trial_type_col].astype(str)
if extra_cols:
for col in extra_cols:
if col in log_df.columns:
events[col] = log_df[col]
events = events.sort_values("onset").reset_index(drop=True)
return events
# Demonstration with synthetic log data
np.random.seed(0)
n_trials = 40
synthetic_log = pd.DataFrame(
{
"onset_s": np.sort(np.random.uniform(2.0, 100.0, n_trials)),
"condition": np.random.choice(["standard", "deviant"], n_trials, p=[0.8, 0.2]),
"rt_s": np.where(
np.random.rand(n_trials) < 0.85,
np.random.normal(0.45, 0.12, n_trials),
np.nan,
),
"correct": np.random.choice([0, 1], n_trials, p=[0.15, 0.85]),
}
)
synthetic_log.to_csv("/tmp/example_log.csv", index=False)
events_df = log_to_events_tsv(
log_csv="/tmp/example_log.csv",
onset_col="onset_s",
trial_type_col="condition",
extra_cols=["rt_s", "correct"],
)
print(events_df.head(8).to_string())
# Save to BIDS location
output_tsv = Path("my_bids_dataset/sub-01/ses-01/eeg") / \
"sub-01_ses-01_task-oddball_run-1_events.tsv"
output_tsv.parent.mkdir(parents=True, exist_ok=True)
events_df.to_csv(output_tsv, sep="\t", index=False)
print(f"Saved: {output_tsv}")
Gorgolewski, K. J. et al. (2016). The brain imaging data structure, a format for organizing and
describing outputs of neuroimaging experiments. Scientific Data, 3, 160044.
Holdgraf, C. et al. (2019). iEEG-BIDS, extending the Brain Imaging Data Structure
specification to human intracranial electrophysiology. Scientific Data, 6, 102.
Appelhoff, S. et al. (2019). MNE-BIDS: Organizing electrophysiological data into the BIDS
format and facilitating their analysis. Journal of Open Source Software, 4(44), 1896.
Interpreting these results: The layout should index 4 EEG files (2 subjects × 2 sessions).
Run bids-validator example_bids_dataset to confirm zero errors.
Example 2: Query an Existing OpenNeuro Dataset and Build an Analysis Table
Scenario: After downloading ds003490 (a public BIDS EEG dataset), build a pandas table
linking every EEG file to its events.tsv and participant demographics.
Interpreting these results: Use the inventory DataFrame to loop over files for batch
preprocessing. The n_deviant column confirms event coding is consistent across subjects.
Last updated: 2026-03-17 | Maintainer: @xjtulycIssues: GitHub Issues