File Naming Conventions¶
NeuroDAGs has no database: an output’s file name is its identity. Caching is
existence-based (a derivative is “done” for a file when a matching output file exists), chained
derivatives are found by name, and dataframe columns are built from names. This page lists every
naming rule and restriction in one place. Most of them are enforced: neurodags validate
reports problems in your derivative names before anything runs.
NeuroDAGs is agnostic to your file-naming convention: it keeps your source file names (BIDS or
not) and only appends its own markers — @ (source/derivative separator), the derivative name
and the artifact suffix — plus &, which replaces @ when a derivative is itself used as a
source. Whether outputs look BIDS-like depends on how your inputs are named.
Note
The naming rules on this page describe the current behaviour. A redesign is planned for 0.4.0 (#49); until then they are kept stable so existing caches stay valid.
Rules at a glance¶
Do |
Don’t |
|---|---|
Name derivatives in CamelCase ( |
Use |
If you use |
Define two derivatives whose on-disk names match, even only up to case ( |
Reference derivatives by their YAML name: |
Reference the on-disk name ( |
Glob outputs by on-disk name: |
Glob by YAML name ( |
Start custom node artifact keys with |
Use artifact keys without a leading |
Keep raw source file names free of |
Have a source named with |
Use xarray dimension names without |
Use dimension names like |
Treat |
Store your own files in |
Output file names¶
Every saved artifact is written to
<reference_base>@<OnDiskName><artifact suffix>
<reference_base>identifies the source file (see Output layout). It keeps the source’s own extension:sub-01_task-rest_eeg.vhdr@....@separates the source from the derivative. There is exactly one@per output file name, soname.split("@", 1)(orrsplit) always gives(source, derivative part).<OnDiskName>is the derivative name converted for disk (see below).<artifact suffix>is the key of each artifact the last node returns, appended verbatim:.fif,.nc,.json,.report.html,.EO_baseline.fif(multi-dot suffixes are fine). It must start with.: a derivative counts as cached when any file matches<reference_base>@<OnDiskName>.*.
A derivative with several artifacts writes several files sharing the same prefix; the presence of
any of them counts as cached (so a run interrupted mid-write is not redone; use
overwrite: true or delete the files).
Marker files¶
Two marker files share the output prefix and are never counted as cached outputs:
File |
Written when |
Effect |
|---|---|---|
|
a node of the derivative raised |
Contains the error. The derivative is retried on the next run unless |
|
a node raised |
The derivative (and derivatives depending on it) is skipped for this file until the marker is deleted or |
save: false derivatives write neither outputs nor markers.
Derivative names¶
The on-disk name is derived from the name in DerivativeDefinitions
(neurodags.utils.snake_to_camel):
A name without
_is used as is:BandPower→@BandPower,psd-welch→@psd-welch(-is left unchanged).A name with
_is split on_, each part is title-cased (Pythonstr.title()) and the parts are joined:fit_w_l06→@FitWL06,band_power→@BandPower,Fit_A→@FitA,EO_Features→@EoFeatures(note the lower-casedo).str.title()lowercases the rest of each_-separated part, so capitals inside a part are lost:Ep_DetrendedFluctuation→@EpDetrendedfluctuation,ICA_cleaned→@IcaCleaned. Checkneurodags validate(orsnake_to_camel(name)) for the exact name to search for.
The conversion keeps the derivative name a single token without _, which many file-naming
conventions (including BIDS) use as a separator. Names with _ are accepted; just look for their
files under the converted name. The conversion is applied consistently for writing and reading,
so existing caches keep working.
Everything except the file names uses the YAML name: DerivativeList, --derivative,
reuse steps (derivative: fit_w_l06.nc), dataframe columns and log messages. The on-disk name is
not an alias: derivative: FitWL06.nc is an unknown derivative (it only matches by accident when
the files already exist).
neurodags validate lists every derivative whose on-disk name differs from its YAML name:
on-disk names differing from YAML names (1):
fit_w_l06 -> files named @FitWL06
Rejected names¶
neurodags validate, run, dry-run, status, count-inputs, dataframe and the Python
entry points (run_pipeline, iterate_derivative_pipeline, build_derivative_dataframe) check
every name in DerivativeDefinitions (not only those selected to run) and fail with a
ValueError before anything is written when:
Problem |
Why |
|---|---|
Two names map to the same on-disk name, e.g. |
They would read and overwrite each other’s cached files, silently returning the wrong data. |
Two on-disk names differ only in case, e.g. |
Same file on case-insensitive filesystems (macOS, Windows). |
The name contains |
|
The name contains |
Breaks the one- |
The name contains |
Path separator: outputs would go to a non-existent subdirectory. |
The name contains |
Glob characters in the cache lookup: |
The name is |
|
Important
The collision check is per pipeline configuration. Two different pipeline files writing to
the same derivatives_path are not checked against each other: if one defines band_power and the
other BandPower, both use @BandPower files and silently share cached outputs. Use distinct
names (or distinct derivatives_paths) across pipelines that share an output directory.
Other names (with _, -, spaces, non-ASCII, …) are accepted. & is accepted too but best
avoided: it is the replacement for @ in chained source names, so it makes those file names
harder to read back.
Derivative references (derivative: Name.ext)¶
A reuse step value is split at its first .:
Reference |
Derivative |
Artifact selected |
|---|---|---|
|
the raw input file |
— |
|
|
all artifacts (which one a node gets follows the |
|
|
the |
|
|
the |
The part before the first . is the YAML name; the rest selects the artifact. When the derivative
is read from disk, the rest is matched against the end of the file names, so CleanPrep.fif
would also match a .EO_baseline.fif artifact; the first match in sorted order is used (with a
warning if several match). When it was just computed in memory, the artifact key must equal
.<rest> exactly. Use the full artifact suffix to get the same artifact either way.
Other reserved strings in node args¶
Form |
Meaning |
|---|---|
|
output of step |
|
one field of a mapping returned by step |
|
dataset variable from the active dataset’s |
|
Python expression (with |
Any other string is passed to the node unchanged. Strings starting with id.<digits>. that are not
a valid id.N.field raise an error instead of being passed through.
Output layout¶
With
derivatives_path: NeuroDAGs takes the common root of all files matched by the dataset (the deepest directory containing all of them) and mirrors each file’s path relative to it underderivatives_path:<derivatives_path>/<path relative to common root>@<OnDiskName><suffix>. For a dataset matching a single file, the common root is that file’s directory, so outputs land at<derivatives_path>/<file name>@.... The common root depends on which files match: changingfile_pattern(orexclude_pattern) so the matched set has a different common root moves where outputs are expected (making this layout stable is tracked in #50).Without
derivatives_path: outputs are written next to the source file (<source path>@<OnDiskName><suffix>).@in the source basename becomes&: when the source file name already contains@(typically a NeuroDAGs output used as the input of another pipeline), every@in the basename (not in directory names) is replaced by&, keeping a single@per output name. This is not reversible:a@b.vhdranda&b.vhdrin the same folder would share outputs.code/:neurodags runcopies the pipeline YAML,new_definitionsfiles and datasets YAML, plusneurodags_env.json, into<derivatives_path>/code/(see Usage).
Dataframe column names¶
build_derivative_dataframe / neurodags dataframe name columns after the YAML name:
<yaml name><artifact suffix> # one column per artifact
<yaml name><artifact suffix>@<flatten suffix> # one column per element of a flattened DataArray
A .nc artifact holding an N-dimensional xarray.DataArray is flattened into one column per
element. The flatten suffix is dim-value pairs joined by _, with dimensions sorted
alphabetically (0-d arrays get no @ part). Coordinate values are escaped so the column can be
split back:
In the coordinate value |
Written as |
|---|---|
|
|
|
|
|
|
|
|
Dimension names are not escaped, which is why they should not contain _, - or @. The
, → . replacement is one-way: a . in a column may have been either.
Reading a column back. For band_power.nc@freqbands-alpha|low_spaces-Fp1~F3:
Split at the first
@:band_power.nc(YAML name + artifact suffix) andfreqbands-alpha|low_spaces-Fp1~F3.Split the flatten suffix on
_:freqbands-alpha|low,spaces-Fp1~F3.Split each pair at the first
-and unescape the value (~→-,|→_,$→@):freqbands = alpha_low,spaces = Fp1-F3.
In long format (output_format="long"), the same string is in the derivative column.
Worked example¶
pipeline.yml defines clean_prep (on disk CleanPrep, a .fif plus a .report.html) and
band_power (on disk BandPower, a .nc with dimensions spaces × freqbands); the dataset
matches rawdata/**/*.vhdr and sets derivatives_path: derivatives:
rawdata/ # common root of the matched files
├── sub-01/eeg/sub-01_task-rest_eeg.vhdr
└── sub-02/eeg/sub-02_task-rest_eeg.vhdr
derivatives/
├── code/ # config snapshot (neurodags run)
│ ├── pipeline.yml
│ ├── datasets.yml
│ └── neurodags_env.json
├── sub-01/eeg/
│ ├── sub-01_task-rest_eeg.vhdr@CleanPrep.fif
│ ├── sub-01_task-rest_eeg.vhdr@CleanPrep.report.html
│ └── sub-01_task-rest_eeg.vhdr@BandPower.nc
└── sub-02/eeg/
└── sub-02_task-rest_eeg.vhdr@CleanPrep.error # clean_prep failed; band_power not written
A second pipeline uses the cleaned files as its sources (file_pattern: derivatives/**/*@CleanPrep.fif, derivatives_path: derivatives2) with a derivative Entropy.
Only sub-01 matches, so the common root is derivatives/sub-01/eeg and the @ of the source
becomes &:
derivatives2/
└── sub-01_task-rest_eeg.vhdr&CleanPrep.fif@Entropy.json
Dataframe columns from the first pipeline (wide format, band_power with for_dataframe: true):
index dataset file_path band_power.nc@freqbands-alpha_spaces-Fp1~F3 band_power.nc@freqbands-alpha_spaces-O1 ...
0 demo rawdata/sub-01/eeg/sub-01_task-rest_eeg.vhdr 0.32 0.29 ...
To find all band_power outputs on disk: find derivatives -name '*@BandPower*'.