Skip to content

Commit 70350ae

Browse files
committed
Add BIDS-first configuration schema and update related scripts
- Introduced `bidsfirst_config_schema.json` to define the configuration structure for BIDS-first processing of DSI-24 data. - Updated `dsi_bids.py`, `event_mapping.py`, and `preprocess_single.py` to reference the new configuration schema. - Added example configurations for Go/No-Go and Five-Point tasks in `configs/examples/`. - Enhanced CI workflow to validate BIDS-first example configs. - Updated `README.md` to include references to the new BIDS-first schema and examples. - Introduced `export_repo_to_xml.py` for improved repository serialization and documentation. - Revised `pyproject.toml` to manage project dependencies and development tools more effectively.
1 parent 144ed0e commit 70350ae

File tree

16 files changed

+644
-77
lines changed

16 files changed

+644
-77
lines changed

.github/workflows/ci.yml

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
pre-commit run --all-files
2727
- name: Run tests
2828
run: pytest -q
29-
- name: Validate configs
29+
- name: Validate pipeline configs (modular)
3030
run: |
3131
python - <<'PY'
3232
import json, sys, yaml
@@ -41,6 +41,9 @@ jobs:
4141
continue
4242
if 'drafts' in p.parts:
4343
continue
44+
if 'examples' in p.parts:
45+
# examples are BIDS-first configs validated in a separate step
46+
continue
4447
try:
4548
if p.suffix.lower() == '.json':
4649
cfg = json.loads(p.read_text())
@@ -59,3 +62,37 @@ jobs:
5962
print(f" - {loc}: {err.message}")
6063
sys.exit(0 if ok else 1)
6164
PY
65+
66+
- name: Validate BIDS-first example configs
67+
run: |
68+
python - <<'PY'
69+
import json, sys
70+
from pathlib import Path
71+
from jsonschema import Draft7Validator
72+
schema_path = Path('bidsfirst_config_schema.json')
73+
if not schema_path.exists():
74+
print('[skip] bidsfirst_config_schema.json not found')
75+
raise SystemExit(0)
76+
schema = json.loads(schema_path.read_text())
77+
validator = Draft7Validator(schema)
78+
ok = True
79+
ex_dir = Path('configs/examples')
80+
if not ex_dir.exists():
81+
print('[skip] configs/examples not present')
82+
raise SystemExit(0)
83+
for p in ex_dir.rglob('*.json'):
84+
try:
85+
cfg = json.loads(p.read_text())
86+
except Exception as e:
87+
print(f"[invalid parse] {p}: {e}")
88+
ok = False
89+
continue
90+
errors = sorted(validator.iter_errors(cfg), key=lambda e: e.path)
91+
if errors:
92+
ok = False
93+
print(f"[schema errors] {p}")
94+
for err in errors:
95+
loc = "/".join(str(x) for x in err.path) or '<root>'
96+
print(f" - {loc}: {err.message}")
97+
sys.exit(0 if ok else 1)
98+
PY

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ This repository is designed for both single-subject and multi-subject pipelines,
7979
# optional: dev tools
8080
pip install -r requirements-dev.txt # adds pytest, black, flake8, etc.
8181
```
82+
Or install as a package for a cleaner setup:
83+
```bash
84+
pip install -e . # users
85+
pip install -e .[dev] # contributors (adds tests/docs tools)
86+
```
8287
3. **Supported Python**:
8388
- We test on Python 3.10–3.12 (recommended with MNE 1.5+).
8489
## Usage Examples
@@ -266,6 +271,9 @@ python preprocess_batch.py \
266271
--task gonogo --n-jobs 1
267272
```
268273
269-
See `configs/examples/` for Go/No‑Go and Five‑Point examples, and `config_schema.json` for the schema driving both BIDSify and preprocessing.
274+
See `configs/examples/` for Go/No‑Go and Five‑Point examples.
275+
Schemas:
276+
- BIDS‑first examples: `bidsfirst_config_schema.json`
277+
- Modular pipeline: `scr/config_schema.json`
270278
271279

configs/examples/fivepoint.json

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"project": {"name": "neurotheque-examples", "version": "0.1.0"},
3+
"paths": {
4+
"source_root": "./data/input",
5+
"bids_root": "./derivatives/bids",
6+
"derivatives_root": null,
7+
"overwrite": false
8+
},
9+
"bids": {
10+
"manufacturer": "Wearable Sensing",
11+
"manufacturersModelName": "DSI-24",
12+
"line_freq": 60,
13+
"montage": "standard_1020",
14+
"eeg_reference": "average",
15+
"dataset_description": {"Name": "DSI-24 Example", "BIDSVersion": "1.8.0"}
16+
},
17+
"filename_parsing": {
18+
"patterns": [
19+
"(?P<subject>\\d+)[_-](?P<session>\\d+).*?(?P<task>5pt).*?(?P<run>\\d+)",
20+
"sub[-_](?P<subject>\\d+).*?ses[-_](?P<session>\\d+).*?task[-_](?P<task>[A-Za-z0-9]+).*?run[-_](?P<run>\\d+)"
21+
],
22+
"fallbacks": {"subject": "01", "session": "001", "task": "5pt", "run": "01"}
23+
},
24+
"events": {
25+
"trigger_channel": "Trigger",
26+
"default_task": "5pt",
27+
"tasks": {
28+
"5pt": {
29+
"event_map": {"stim": [1], "response": [2]},
30+
"onset_shift_sec": 0.0,
31+
"debounce_ms": 5.0,
32+
"epoching": {"tmin": -0.2, "tmax": 0.8, "baseline": [null, 0.0], "preload": true, "reject_by_annotation": true}
33+
}
34+
}
35+
},
36+
"preprocessing": {
37+
"resample_sfreq": null,
38+
"filter": {"l_freq": 1.0, "h_freq": 40.0},
39+
"notch": {"freqs": [60, 120]},
40+
"bad_channels": [],
41+
"reref": "average",
42+
"ica": {"method": "fastica", "n_components": "auto", "ecg": true, "eog": true, "reject_by_annotation": true},
43+
"autoreject": {"enabled": false, "method": "local"},
44+
"metrics": {"psd": true, "epochs_rejection": true},
45+
"save_intermediates": true
46+
},
47+
"report": {"enabled": true, "title": "Neurothèque Preprocessing Report", "sections": ["psd", "ica", "epochs"], "include_logs": true},
48+
"logging": {"level": "INFO", "json_log": true, "log_to_console": true},
49+
"batch": {"mode": "single", "n_jobs": 1}
50+
}
51+

configs/examples/gonogo.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"project": {"name": "neurotheque-examples", "version": "0.1.0"},
3+
"paths": {
4+
"source_root": "./data/input",
5+
"bids_root": "./derivatives/bids",
6+
"derivatives_root": null,
7+
"overwrite": false
8+
},
9+
"bids": {
10+
"manufacturer": "Wearable Sensing",
11+
"manufacturersModelName": "DSI-24",
12+
"line_freq": 60,
13+
"montage": "standard_1020",
14+
"eeg_reference": "average",
15+
"dataset_description": {"Name": "DSI-24 Example", "BIDSVersion": "1.8.0"}
16+
},
17+
"filename_parsing": {
18+
"patterns": [
19+
"(?P<subject>\\d+)[_-](?P<session>\\d+).*?(?P<task>gonogo).*?(?P<run>\\d+)",
20+
"sub[-_](?P<subject>\\d+).*?ses[-_](?P<session>\\d+).*?task[-_](?P<task>[A-Za-z0-9]+).*?run[-_](?P<run>\\d+)"
21+
],
22+
"fallbacks": {"subject": "01", "session": "001", "task": "gonogo", "run": "01"}
23+
},
24+
"events": {
25+
"trigger_channel": "Trigger",
26+
"default_task": "gonogo",
27+
"tasks": {
28+
"gonogo": {
29+
"event_map": {"go": [1], "nogo": [2], "response": [8]},
30+
"aliases": {"G": 1, "N": 2},
31+
"onset_shift_sec": 0.0,
32+
"debounce_ms": 5.0,
33+
"epoching": {"tmin": -0.2, "tmax": 0.8, "baseline": [null, 0.0], "preload": true, "reject_by_annotation": true}
34+
}
35+
}
36+
},
37+
"preprocessing": {
38+
"resample_sfreq": null,
39+
"filter": {"l_freq": 1.0, "h_freq": 40.0},
40+
"notch": {"freqs": [60, 120]},
41+
"bad_channels": [],
42+
"reref": "average",
43+
"ica": {"method": "fastica", "n_components": "auto", "ecg": true, "eog": true, "reject_by_annotation": true},
44+
"autoreject": {"enabled": false, "method": "local"},
45+
"metrics": {"psd": true, "epochs_rejection": true},
46+
"save_intermediates": true
47+
},
48+
"report": {"enabled": true, "title": "Neurothèque Preprocessing Report", "sections": ["psd", "ica", "epochs"], "include_logs": true},
49+
"logging": {"level": "INFO", "json_log": true, "log_to_console": true},
50+
"batch": {"mode": "single", "n_jobs": 1}
51+
}
52+

dsi_bids.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ def _iter_edfs(root: Path) -> Iterable[Path]:
409409

410410
def main(argv: Optional[List[str]] = None) -> int:
411411
p = argparse.ArgumentParser(description="BIDSify a folder of DSI‑24 EDF files (JSON‑driven).")
412-
p.add_argument("--config", required=True, help="Path to JSON config (see config_schema.json).")
412+
p.add_argument("--config", required=True, help="Path to JSON config (see bidsfirst_config_schema.json).")
413413
p.add_argument("--source", required=True, help="Folder containing .edf files (recursed).")
414414
p.add_argument("--bids-root", required=True, help="Output BIDS root.")
415415
args = p.parse_args(argv)
@@ -452,4 +452,3 @@ def main(argv: Optional[List[str]] = None) -> int:
452452

453453
if __name__ == "__main__":
454454
raise SystemExit(main())
455-

event_mapping.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
- mapped events (with debounced / onset-shifted samples)
66
- event_id {label -> code} suitable for MNE/MNE-BIDS
77
8-
Expected task JSON structure (per config_schema.json):
8+
Expected task JSON structure (per bidsfirst_config_schema.json):
99
{
1010
"event_map": { "GO": [1], "NOGO": [2], "START": [7] },
1111
"aliases?": { "Start": 7 },
@@ -123,4 +123,3 @@ def map_events_from_config(
123123
ev_keep = ev_keep[np.argsort(ev_keep[:, 0])]
124124

125125
return ev_keep, dict(event_id)
126-

export_repo_to_xml.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""Compat wrapper for tools/dev/export_repo_to_xml.py.
2+
3+
Allows calling from repo root:
4+
python export_repo_to_xml.py --out repo.xml [--args]
5+
"""
6+
7+
from __future__ import annotations
8+
9+
import runpy
10+
from pathlib import Path
11+
12+
13+
if __name__ == "__main__":
14+
script = Path(__file__).parent / "tools" / "dev" / "export_repo_to_xml.py"
15+
runpy.run_path(str(script), run_name="__main__")

notebooks/preprocessing/gonogo_intermediate_preprocessing.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,7 +1326,7 @@
13261326
],
13271327
"metadata": {
13281328
"kernelspec": {
1329-
"display_name": "eeg_analysis",
1329+
"display_name": "neuropipe",
13301330
"language": "python",
13311331
"name": "python3"
13321332
},
@@ -1340,7 +1340,7 @@
13401340
"name": "python",
13411341
"nbconvert_exporter": "python",
13421342
"pygments_lexer": "ipython3",
1343-
"version": "3.10.15"
1343+
"version": "3.11.11"
13441344
}
13451345
},
13461346
"nbformat": 4,

preprocess_single.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ def preprocess_one(
293293

294294
def main(argv: Optional[list[str]] = None) -> int:
295295
ap = argparse.ArgumentParser(description="Preprocess a single BIDS run (JSON-driven).")
296-
ap.add_argument("--config", required=True, help="JSON config (see config_schema.json)")
296+
ap.add_argument("--config", required=True, help="JSON config (see bidsfirst_config_schema.json)")
297297
ap.add_argument("--bids-root", required=True)
298298
ap.add_argument("--sub", required=True)
299299
ap.add_argument("--ses")
@@ -310,4 +310,3 @@ def main(argv: Optional[list[str]] = None) -> int:
310310

311311
if __name__ == "__main__":
312312
raise SystemExit(main())
313-

0 commit comments

Comments
 (0)