neurodags 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- neurodags-0.1.1/.gitignore +57 -0
- neurodags-0.1.1/LICENSE +21 -0
- neurodags-0.1.1/PKG-INFO +316 -0
- neurodags-0.1.1/README.md +255 -0
- neurodags-0.1.1/pyproject.toml +144 -0
- neurodags-0.1.1/src/neurodags/__init__.py +12 -0
- neurodags-0.1.1/src/neurodags/_version.py +24 -0
- neurodags-0.1.1/src/neurodags/dag.py +824 -0
- neurodags-0.1.1/src/neurodags/datasets.py +789 -0
- neurodags-0.1.1/src/neurodags/definitions.py +39 -0
- neurodags-0.1.1/src/neurodags/derivatives/__init__.py +160 -0
- neurodags-0.1.1/src/neurodags/derivatives/pipeline.py +48 -0
- neurodags-0.1.1/src/neurodags/derivatives/pipeline.yml +109 -0
- neurodags-0.1.1/src/neurodags/example.py +20 -0
- neurodags-0.1.1/src/neurodags/iterators.py +181 -0
- neurodags-0.1.1/src/neurodags/loaders.py +181 -0
- neurodags-0.1.1/src/neurodags/loggers.py +196 -0
- neurodags-0.1.1/src/neurodags/nodes/__init__.py +116 -0
- neurodags-0.1.1/src/neurodags/nodes/antropy.py +117 -0
- neurodags-0.1.1/src/neurodags/nodes/descriptive.py +283 -0
- neurodags-0.1.1/src/neurodags/nodes/dummy.py +33 -0
- neurodags-0.1.1/src/neurodags/nodes/factories.py +875 -0
- neurodags-0.1.1/src/neurodags/nodes/loader.py +62 -0
- neurodags-0.1.1/src/neurodags/nodes/neurokit.py +364 -0
- neurodags-0.1.1/src/neurodags/nodes/operations.py +277 -0
- neurodags-0.1.1/src/neurodags/nodes/preprocessing.py +153 -0
- neurodags-0.1.1/src/neurodags/nodes/spectral.py +1274 -0
- neurodags-0.1.1/src/neurodags/orchestrators.py +842 -0
- neurodags-0.1.1/src/neurodags/utils.py +391 -0
- neurodags-0.1.1/src/neurodags/visualization.py +221 -0
- neurodags-0.1.1/src/neurodags/writers.py +62 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
dist/
|
|
13
|
+
.eggs/
|
|
14
|
+
*.egg-info/
|
|
15
|
+
*.egg
|
|
16
|
+
|
|
17
|
+
docs/sg_execution_times.rst
|
|
18
|
+
src/*/_version.py
|
|
19
|
+
_data/
|
|
20
|
+
_outputs/
|
|
21
|
+
data/
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
.venv/
|
|
25
|
+
.venv*/
|
|
26
|
+
venv/
|
|
27
|
+
.env/
|
|
28
|
+
|
|
29
|
+
# Installer logs
|
|
30
|
+
pip-log.txt
|
|
31
|
+
pip-delete-this-directory.txt
|
|
32
|
+
|
|
33
|
+
# Unit test / coverage reports
|
|
34
|
+
htmlcov/
|
|
35
|
+
.tox/
|
|
36
|
+
.nox/
|
|
37
|
+
.coverage
|
|
38
|
+
.coverage.*
|
|
39
|
+
.cache/
|
|
40
|
+
.pytest_cache/
|
|
41
|
+
|
|
42
|
+
# Type checker / linters
|
|
43
|
+
.mypy_cache/
|
|
44
|
+
.ruff_cache/
|
|
45
|
+
|
|
46
|
+
# IDEs and editors
|
|
47
|
+
#.vscode/
|
|
48
|
+
.idea/
|
|
49
|
+
|
|
50
|
+
# Sphinx documentation
|
|
51
|
+
docs/_build/
|
|
52
|
+
docs/auto_examples/
|
|
53
|
+
|
|
54
|
+
# OS files
|
|
55
|
+
.DS_Store
|
|
56
|
+
Thumbs.db
|
|
57
|
+
|
neurodags-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Your Name
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
neurodags-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: neurodags
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A slurm friendly MEEG derivative extraction package leveraging bids-like data organization and DAG processing.
|
|
5
|
+
Project-URL: Homepage, https://github.com/yjmantilla/neurodags
|
|
6
|
+
Project-URL: Documentation, https://yjmantilla.github.io/neurodags/
|
|
7
|
+
Project-URL: Repository, https://github.com/yjmantilla/neurodags
|
|
8
|
+
Project-URL: Issues, https://github.com/yjmantilla/neurodags/issues
|
|
9
|
+
Author-email: Yorguin-Jose Mantilla-Ramos <yjmantilla@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: bids,derivative,eeg,extraction,meeg,meg
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: antropy
|
|
25
|
+
Requires-Dist: dash
|
|
26
|
+
Requires-Dist: fooof
|
|
27
|
+
Requires-Dist: h5py
|
|
28
|
+
Requires-Dist: imageio
|
|
29
|
+
Requires-Dist: joblib
|
|
30
|
+
Requires-Dist: mne
|
|
31
|
+
Requires-Dist: mne-bids
|
|
32
|
+
Requires-Dist: netcdf4
|
|
33
|
+
Requires-Dist: networkx
|
|
34
|
+
Requires-Dist: neurokit2
|
|
35
|
+
Requires-Dist: pandas
|
|
36
|
+
Requires-Dist: plotly
|
|
37
|
+
Requires-Dist: pybv>=0.7.3
|
|
38
|
+
Requires-Dist: pydantic
|
|
39
|
+
Requires-Dist: pyyaml
|
|
40
|
+
Requires-Dist: structlog
|
|
41
|
+
Requires-Dist: xarray
|
|
42
|
+
Requires-Dist: xpublish
|
|
43
|
+
Provides-Extra: dev
|
|
44
|
+
Requires-Dist: black>=24.3; extra == 'dev'
|
|
45
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
46
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
47
|
+
Requires-Dist: pre-commit>=3.7; extra == 'dev'
|
|
48
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
49
|
+
Provides-Extra: docs
|
|
50
|
+
Requires-Dist: furo>=2024.1.29; extra == 'docs'
|
|
51
|
+
Requires-Dist: matplotlib>=3.7; extra == 'docs'
|
|
52
|
+
Requires-Dist: myst-parser>=2.0; extra == 'docs'
|
|
53
|
+
Requires-Dist: sphinx-autoapi; extra == 'docs'
|
|
54
|
+
Requires-Dist: sphinx-autodoc-typehints>=2.1; extra == 'docs'
|
|
55
|
+
Requires-Dist: sphinx-gallery>=0.16; extra == 'docs'
|
|
56
|
+
Requires-Dist: sphinx>=7.3; extra == 'docs'
|
|
57
|
+
Provides-Extra: test
|
|
58
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'test'
|
|
59
|
+
Requires-Dist: pytest>=8.2; extra == 'test'
|
|
60
|
+
Description-Content-Type: text/markdown
|
|
61
|
+
|
|
62
|
+
[](https://github.com/yjmantilla/neurodags/actions?query=workflow%3ACI)
|
|
63
|
+
[](https://github.com/yjmantilla/neurodags/actions?query=workflow%3ADocs)
|
|
64
|
+
[](https://app.codecov.io/gh/yjmantilla/neurodags)
|
|
65
|
+
|
|
66
|
+
# NeuroDAGs
|
|
67
|
+
|
|
68
|
+
**An Extensible and Declarative DAG Framework for Reproducible Neuroscience Workflows**
|
|
69
|
+
|
|
70
|
+
M/EEG studies generate many interdependent intermediate derivatives. Recomputing full pipelines is wasteful; reusing valid intermediates is non-trivial. Large-scale studies require reproducible, extensible, and efficient workflows. NeuroDAGs addresses this with a declarative, graph-based framework for scalable and reusable derivative computation.
|
|
71
|
+
|
|
72
|
+
**[Poster BRaIN Symposium 2026 Montreal](https://canva.link/c2b2fm0mk2wotwq)**
|
|
73
|
+
|
|
74
|
+
## Core Idea
|
|
75
|
+
|
|
76
|
+
Pipelines are defined as a **directed acyclic graph (DAG)** of computation nodes that output reusable derivatives, executed for each input file.
|
|
77
|
+
|
|
78
|
+
## Design Principles
|
|
79
|
+
|
|
80
|
+
- **Reproducible, transparent workflows** defined declaratively in YAML — version-controllable and LLM-friendly.
|
|
81
|
+
- **Uniform node abstraction** — preprocessing, features, and any custom nodes are treated identically.
|
|
82
|
+
- **Directory-agnostic** — outputs mirror inputs' organization. Derivatives are labeled with a `@DerivativeName` suffix.
|
|
83
|
+
- **xarray-centered outputs** — derivatives stored as language-agnostic, metadata-rich, dimension-aware xarray → NetCDF.
|
|
84
|
+
- **Graph-based reuse** — if a derivative is already computed and `overwrite=False`, it is skipped automatically.
|
|
85
|
+
|
|
86
|
+
## Features
|
|
87
|
+
|
|
88
|
+
- Agnostic to data organization / directory hierarchy
|
|
89
|
+
- SLURM / HPC friendly with file-level parallelism via joblib
|
|
90
|
+
- Graph-based caching: skip already-computed derivatives
|
|
91
|
+
- Extensible node system — add nodes without forking the package
|
|
92
|
+
- YAML-based declarative configuration
|
|
93
|
+
- Built-in nodes for preprocessing, spectral analysis, entropy, complexity, and data transformations
|
|
94
|
+
- Dataframe assembly (wide or long format) from derivative artifacts
|
|
95
|
+
- Dry-run mode — inspect planned computations without executing
|
|
96
|
+
- Built-in Dash-Plotly explorer for `.fif` and `.nc` files
|
|
97
|
+
|
|
98
|
+
## Installation
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install neurodags
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
With [uv](https://docs.astral.sh/uv/) (recommended):
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
uv add neurodags
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Quickstart
|
|
111
|
+
|
|
112
|
+
See the [quickstart example](https://yjmantilla.github.io/neurodags/auto_examples/plot_quickstart_synthetic.html) — full synthetic pipeline, no real data required.
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
git clone https://github.com/yjmantilla/neurodags
|
|
118
|
+
cd neurodags
|
|
119
|
+
uv sync --all-extras --all-groups # creates .venv and installs all deps incl. dev/test/docs
|
|
120
|
+
uv run pre-commit install
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Key commands (all via `uv run`):
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
uv run ruff check src/ # lint (fix: uv run ruff check src/ --fix)
|
|
127
|
+
uv run black --check . # format check (fix: uv run black .)
|
|
128
|
+
uv run pytest -q # run tests
|
|
129
|
+
uv run pytest -s -q --no-cov --pdb # debug a failing test
|
|
130
|
+
|
|
131
|
+
uv run sphinx-build -b html docs docs/_build/html -W --keep-going # build docs
|
|
132
|
+
rm -rf docs/_build # clean docs
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
> **No uv?** Install it with `pip install uv` or `curl -Ls https://astral.sh/uv/install.sh | sh`.
|
|
136
|
+
> All commands above work with plain `python`/`pip` too — swap `uv run` → activate `.venv`, `uv sync` → `pip install -e .[dev,test,docs]`.
|
|
137
|
+
|
|
138
|
+
## Project Structure
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
my_project/
|
|
142
|
+
├── datasets.yml # Dataset sources and paths
|
|
143
|
+
├── pipeline.yml # Derivative definitions and execution list
|
|
144
|
+
└── custom_nodes.py # Optional custom node definitions
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Quick Example
|
|
148
|
+
|
|
149
|
+
**`datasets.yml`**
|
|
150
|
+
```yaml
|
|
151
|
+
my_dataset:
|
|
152
|
+
name: MyDataset
|
|
153
|
+
file_pattern:
|
|
154
|
+
local: data/**/*.vhdr
|
|
155
|
+
hpc: /cluster/BIDS/**/*.vhdr
|
|
156
|
+
derivatives_path:
|
|
157
|
+
local: outputs/
|
|
158
|
+
hpc: /cluster/scratch/out
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**`pipeline.yml`**
|
|
162
|
+
```yaml
|
|
163
|
+
datasets: datasets.yml
|
|
164
|
+
mount_point: local
|
|
165
|
+
new_definitions: custom_nodes.py # optional
|
|
166
|
+
|
|
167
|
+
DerivativeDefinitions:
|
|
168
|
+
CleanedEEG:
|
|
169
|
+
nodes:
|
|
170
|
+
- id: 0
|
|
171
|
+
derivative: SourceFile
|
|
172
|
+
- id: 1
|
|
173
|
+
node: basic_preprocessing
|
|
174
|
+
args:
|
|
175
|
+
mne_object: id.0
|
|
176
|
+
resample: 256
|
|
177
|
+
filter_args:
|
|
178
|
+
l_freq: 0.5
|
|
179
|
+
h_freq: 110
|
|
180
|
+
|
|
181
|
+
PowerSpectrum:
|
|
182
|
+
for_dataframe: True
|
|
183
|
+
nodes:
|
|
184
|
+
- id: 0
|
|
185
|
+
derivative: CleanedEEG.fif
|
|
186
|
+
- id: 1
|
|
187
|
+
node: mne_spectrum_array
|
|
188
|
+
args:
|
|
189
|
+
meeg: id.0
|
|
190
|
+
method: multitaper
|
|
191
|
+
|
|
192
|
+
DerivativeList:
|
|
193
|
+
- CleanedEEG
|
|
194
|
+
- PowerSpectrum
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Python**
|
|
198
|
+
```python
|
|
199
|
+
from neurodags.loaders import load_configuration
|
|
200
|
+
from neurodags.orchestrators import iterate_derivative_pipeline
|
|
201
|
+
|
|
202
|
+
config = load_configuration("pipeline.yml")
|
|
203
|
+
iterate_derivative_pipeline(config, "CleanedEEG")
|
|
204
|
+
iterate_derivative_pipeline(config, "PowerSpectrum")
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Custom Nodes
|
|
208
|
+
|
|
209
|
+
Add nodes without modifying or forking the package:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
# custom_nodes.py
|
|
213
|
+
from neurodags.nodes import register_node
|
|
214
|
+
from neurodags.definitions import Artifact, NodeResult
|
|
215
|
+
|
|
216
|
+
@register_node
|
|
217
|
+
def my_node(data) -> NodeResult:
|
|
218
|
+
result = compute(data)
|
|
219
|
+
return NodeResult(
|
|
220
|
+
artifacts={
|
|
221
|
+
".nc": Artifact(
|
|
222
|
+
item=result,
|
|
223
|
+
writer=lambda path: result.to_netcdf(path),
|
|
224
|
+
),
|
|
225
|
+
},
|
|
226
|
+
)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Key rules:
|
|
230
|
+
1. A node is a function decorated with `@register_node`.
|
|
231
|
+
2. It returns a `NodeResult`.
|
|
232
|
+
3. A `NodeResult` contains `artifacts` — a dict mapping file extension to `Artifact(item, writer)`.
|
|
233
|
+
|
|
234
|
+
## Dataframe Assembly
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from neurodags.orchestrators import build_derivative_dataframe
|
|
238
|
+
|
|
239
|
+
df = build_derivative_dataframe("pipeline.yml", output_format="wide")
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Derivatives marked `for_dataframe: True` are collected automatically. Supports `"wide"` (one row per file) and `"long"` (one row per value) formats.
|
|
243
|
+
|
|
244
|
+
## Parallel Execution
|
|
245
|
+
|
|
246
|
+
```yaml
|
|
247
|
+
# pipeline.yml
|
|
248
|
+
n_jobs: 4 # -1 = all cores, 1 or null = serial
|
|
249
|
+
joblib_backend: loky
|
|
250
|
+
joblib_prefer: processes
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Or via Python:
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
iterate_derivative_pipeline(config, "MyDerivative", n_jobs=4)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Visualization
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
python -m neurodags.visualization path/to/file.fif
|
|
263
|
+
python -m neurodags.visualization path/to/file.nc
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Built-in Dash-Plotly explorer with dimension-aware UI — dropdown per axis, plot types: Line, Scatter, Bar, Heatmap.
|
|
267
|
+
|
|
268
|
+
## Inspection (Dry Run)
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
iterate_derivative_pipeline(config, "MyDerivative", dry_run=True)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Returns a dataframe describing the execution plan without running any nodes. `.error` marker files prevent silent retry of failed runs.
|
|
275
|
+
|
|
276
|
+
## Derivative Flags
|
|
277
|
+
|
|
278
|
+
| Flag | Default | Description |
|
|
279
|
+
|------|---------|-------------|
|
|
280
|
+
| `save` | `True` | Persist artifacts to disk. `False` = compute but don't write. |
|
|
281
|
+
| `overwrite` | `False` | Force recompute even if output exists. |
|
|
282
|
+
| `for_dataframe` | `False` | Include this derivative in `build_derivative_dataframe`. |
|
|
283
|
+
|
|
284
|
+
## Custom Node Definitions
|
|
285
|
+
|
|
286
|
+
Point `new_definitions` to one or more Python files:
|
|
287
|
+
|
|
288
|
+
```yaml
|
|
289
|
+
new_definitions:
|
|
290
|
+
- custom_nodes/my_nodes.py
|
|
291
|
+
- /abs/path/to/other_nodes.py
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Relative paths are resolved from the pipeline YAML location.
|
|
295
|
+
|
|
296
|
+
## Documentation
|
|
297
|
+
|
|
298
|
+
[https://yjmantilla.github.io/neurodags/](https://yjmantilla.github.io/neurodags/)
|
|
299
|
+
|
|
300
|
+
## HDF5 / NetCDF Note
|
|
301
|
+
|
|
302
|
+
If you encounter `RuntimeError: NetCDF: HDF error`:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
uv run pip install --no-binary=h5py h5py
|
|
306
|
+
# or without uv:
|
|
307
|
+
pip install --no-binary=h5py h5py
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Contributing
|
|
311
|
+
|
|
312
|
+
See [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
313
|
+
|
|
314
|
+
## License
|
|
315
|
+
|
|
316
|
+
MIT. See [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
[](https://github.com/yjmantilla/neurodags/actions?query=workflow%3ACI)
|
|
2
|
+
[](https://github.com/yjmantilla/neurodags/actions?query=workflow%3ADocs)
|
|
3
|
+
[](https://app.codecov.io/gh/yjmantilla/neurodags)
|
|
4
|
+
|
|
5
|
+
# NeuroDAGs
|
|
6
|
+
|
|
7
|
+
**An Extensible and Declarative DAG Framework for Reproducible Neuroscience Workflows**
|
|
8
|
+
|
|
9
|
+
M/EEG studies generate many interdependent intermediate derivatives. Recomputing full pipelines is wasteful; reusing valid intermediates is non-trivial. Large-scale studies require reproducible, extensible, and efficient workflows. NeuroDAGs addresses this with a declarative, graph-based framework for scalable and reusable derivative computation.
|
|
10
|
+
|
|
11
|
+
**[Poster BRaIN Symposium 2026 Montreal](https://canva.link/c2b2fm0mk2wotwq)**
|
|
12
|
+
|
|
13
|
+
## Core Idea
|
|
14
|
+
|
|
15
|
+
Pipelines are defined as a **directed acyclic graph (DAG)** of computation nodes that output reusable derivatives, executed for each input file.
|
|
16
|
+
|
|
17
|
+
## Design Principles
|
|
18
|
+
|
|
19
|
+
- **Reproducible, transparent workflows** defined declaratively in YAML — version-controllable and LLM-friendly.
|
|
20
|
+
- **Uniform node abstraction** — preprocessing, features, and any custom nodes are treated identically.
|
|
21
|
+
- **Directory-agnostic** — outputs mirror inputs' organization. Derivatives are labeled with a `@DerivativeName` suffix.
|
|
22
|
+
- **xarray-centered outputs** — derivatives stored as language-agnostic, metadata-rich, dimension-aware xarray → NetCDF.
|
|
23
|
+
- **Graph-based reuse** — if a derivative is already computed and `overwrite=False`, it is skipped automatically.
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- Agnostic to data organization / directory hierarchy
|
|
28
|
+
- SLURM / HPC friendly with file-level parallelism via joblib
|
|
29
|
+
- Graph-based caching: skip already-computed derivatives
|
|
30
|
+
- Extensible node system — add nodes without forking the package
|
|
31
|
+
- YAML-based declarative configuration
|
|
32
|
+
- Built-in nodes for preprocessing, spectral analysis, entropy, complexity, and data transformations
|
|
33
|
+
- Dataframe assembly (wide or long format) from derivative artifacts
|
|
34
|
+
- Dry-run mode — inspect planned computations without executing
|
|
35
|
+
- Built-in Dash-Plotly explorer for `.fif` and `.nc` files
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install neurodags
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
With [uv](https://docs.astral.sh/uv/) (recommended):
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv add neurodags
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quickstart
|
|
50
|
+
|
|
51
|
+
See the [quickstart example](https://yjmantilla.github.io/neurodags/auto_examples/plot_quickstart_synthetic.html) — full synthetic pipeline, no real data required.
|
|
52
|
+
|
|
53
|
+
## Development
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/yjmantilla/neurodags
|
|
57
|
+
cd neurodags
|
|
58
|
+
uv sync --all-extras --all-groups # creates .venv and installs all deps incl. dev/test/docs
|
|
59
|
+
uv run pre-commit install
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Key commands (all via `uv run`):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv run ruff check src/ # lint (fix: uv run ruff check src/ --fix)
|
|
66
|
+
uv run black --check . # format check (fix: uv run black .)
|
|
67
|
+
uv run pytest -q # run tests
|
|
68
|
+
uv run pytest -s -q --no-cov --pdb # debug a failing test
|
|
69
|
+
|
|
70
|
+
uv run sphinx-build -b html docs docs/_build/html -W --keep-going # build docs
|
|
71
|
+
rm -rf docs/_build # clean docs
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
> **No uv?** Install it with `pip install uv` or `curl -Ls https://astral.sh/uv/install.sh | sh`.
|
|
75
|
+
> All commands above work with plain `python`/`pip` too — swap `uv run` → activate `.venv`, `uv sync` → `pip install -e .[dev,test,docs]`.
|
|
76
|
+
|
|
77
|
+
## Project Structure
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
my_project/
|
|
81
|
+
├── datasets.yml # Dataset sources and paths
|
|
82
|
+
├── pipeline.yml # Derivative definitions and execution list
|
|
83
|
+
└── custom_nodes.py # Optional custom node definitions
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Quick Example
|
|
87
|
+
|
|
88
|
+
**`datasets.yml`**
|
|
89
|
+
```yaml
|
|
90
|
+
my_dataset:
|
|
91
|
+
name: MyDataset
|
|
92
|
+
file_pattern:
|
|
93
|
+
local: data/**/*.vhdr
|
|
94
|
+
hpc: /cluster/BIDS/**/*.vhdr
|
|
95
|
+
derivatives_path:
|
|
96
|
+
local: outputs/
|
|
97
|
+
hpc: /cluster/scratch/out
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**`pipeline.yml`**
|
|
101
|
+
```yaml
|
|
102
|
+
datasets: datasets.yml
|
|
103
|
+
mount_point: local
|
|
104
|
+
new_definitions: custom_nodes.py # optional
|
|
105
|
+
|
|
106
|
+
DerivativeDefinitions:
|
|
107
|
+
CleanedEEG:
|
|
108
|
+
nodes:
|
|
109
|
+
- id: 0
|
|
110
|
+
derivative: SourceFile
|
|
111
|
+
- id: 1
|
|
112
|
+
node: basic_preprocessing
|
|
113
|
+
args:
|
|
114
|
+
mne_object: id.0
|
|
115
|
+
resample: 256
|
|
116
|
+
filter_args:
|
|
117
|
+
l_freq: 0.5
|
|
118
|
+
h_freq: 110
|
|
119
|
+
|
|
120
|
+
PowerSpectrum:
|
|
121
|
+
for_dataframe: True
|
|
122
|
+
nodes:
|
|
123
|
+
- id: 0
|
|
124
|
+
derivative: CleanedEEG.fif
|
|
125
|
+
- id: 1
|
|
126
|
+
node: mne_spectrum_array
|
|
127
|
+
args:
|
|
128
|
+
meeg: id.0
|
|
129
|
+
method: multitaper
|
|
130
|
+
|
|
131
|
+
DerivativeList:
|
|
132
|
+
- CleanedEEG
|
|
133
|
+
- PowerSpectrum
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Python**
|
|
137
|
+
```python
|
|
138
|
+
from neurodags.loaders import load_configuration
|
|
139
|
+
from neurodags.orchestrators import iterate_derivative_pipeline
|
|
140
|
+
|
|
141
|
+
config = load_configuration("pipeline.yml")
|
|
142
|
+
iterate_derivative_pipeline(config, "CleanedEEG")
|
|
143
|
+
iterate_derivative_pipeline(config, "PowerSpectrum")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Custom Nodes
|
|
147
|
+
|
|
148
|
+
Add nodes without modifying or forking the package:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
# custom_nodes.py
|
|
152
|
+
from neurodags.nodes import register_node
|
|
153
|
+
from neurodags.definitions import Artifact, NodeResult
|
|
154
|
+
|
|
155
|
+
@register_node
|
|
156
|
+
def my_node(data) -> NodeResult:
|
|
157
|
+
result = compute(data)
|
|
158
|
+
return NodeResult(
|
|
159
|
+
artifacts={
|
|
160
|
+
".nc": Artifact(
|
|
161
|
+
item=result,
|
|
162
|
+
writer=lambda path: result.to_netcdf(path),
|
|
163
|
+
),
|
|
164
|
+
},
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Key rules:
|
|
169
|
+
1. A node is a function decorated with `@register_node`.
|
|
170
|
+
2. It returns a `NodeResult`.
|
|
171
|
+
3. A `NodeResult` contains `artifacts` — a dict mapping file extension to `Artifact(item, writer)`.
|
|
172
|
+
|
|
173
|
+
## Dataframe Assembly
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from neurodags.orchestrators import build_derivative_dataframe
|
|
177
|
+
|
|
178
|
+
df = build_derivative_dataframe("pipeline.yml", output_format="wide")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Derivatives marked `for_dataframe: True` are collected automatically. Supports `"wide"` (one row per file) and `"long"` (one row per value) formats.
|
|
182
|
+
|
|
183
|
+
## Parallel Execution
|
|
184
|
+
|
|
185
|
+
```yaml
|
|
186
|
+
# pipeline.yml
|
|
187
|
+
n_jobs: 4 # -1 = all cores, 1 or null = serial
|
|
188
|
+
joblib_backend: loky
|
|
189
|
+
joblib_prefer: processes
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Or via Python:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
iterate_derivative_pipeline(config, "MyDerivative", n_jobs=4)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Visualization
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
python -m neurodags.visualization path/to/file.fif
|
|
202
|
+
python -m neurodags.visualization path/to/file.nc
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Built-in Dash-Plotly explorer with dimension-aware UI — dropdown per axis, plot types: Line, Scatter, Bar, Heatmap.
|
|
206
|
+
|
|
207
|
+
## Inspection (Dry Run)
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
iterate_derivative_pipeline(config, "MyDerivative", dry_run=True)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Returns a dataframe describing the execution plan without running any nodes. `.error` marker files prevent silent retry of failed runs.
|
|
214
|
+
|
|
215
|
+
## Derivative Flags
|
|
216
|
+
|
|
217
|
+
| Flag | Default | Description |
|
|
218
|
+
|------|---------|-------------|
|
|
219
|
+
| `save` | `True` | Persist artifacts to disk. `False` = compute but don't write. |
|
|
220
|
+
| `overwrite` | `False` | Force recompute even if output exists. |
|
|
221
|
+
| `for_dataframe` | `False` | Include this derivative in `build_derivative_dataframe`. |
|
|
222
|
+
|
|
223
|
+
## Custom Node Definitions
|
|
224
|
+
|
|
225
|
+
Point `new_definitions` to one or more Python files:
|
|
226
|
+
|
|
227
|
+
```yaml
|
|
228
|
+
new_definitions:
|
|
229
|
+
- custom_nodes/my_nodes.py
|
|
230
|
+
- /abs/path/to/other_nodes.py
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Relative paths are resolved from the pipeline YAML location.
|
|
234
|
+
|
|
235
|
+
## Documentation
|
|
236
|
+
|
|
237
|
+
[https://yjmantilla.github.io/neurodags/](https://yjmantilla.github.io/neurodags/)
|
|
238
|
+
|
|
239
|
+
## HDF5 / NetCDF Note
|
|
240
|
+
|
|
241
|
+
If you encounter `RuntimeError: NetCDF: HDF error`:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
uv run pip install --no-binary=h5py h5py
|
|
245
|
+
# or without uv:
|
|
246
|
+
pip install --no-binary=h5py h5py
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Contributing
|
|
250
|
+
|
|
251
|
+
See [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
MIT. See [`LICENSE`](LICENSE).
|