Usage¶
The general usage of ffrprep is to preprocess frequency-following response (FFR) EEG data in BIDS format.
ffrprep is a BIDS-compatible preprocessing workflow designed specifically for FFR experiments, providing
automated preprocessing pipelines that include data loading, filtering, artifact removal, and report generation
for neurophysiological data analysis.
The exact command to run ffrprep depends on the Installation method and user. Regarding the latter, ffrprep
can either be used as a command line tool or directly within python. Please refer to the Tutorial for a more detailed walkthrough.
Here’s a very conceptual example of running ffrprep via CLI:
ffrprep
ffrprep optional_arguments
and here from within python:
from ffrprep import ffrprep_function
from ffrprep import ffrprep_function
result = ffrprep_function(input)
result = ffrprep_function(input, optional_arguments)
Below, we will focus on the CLI version. For programmatic use,
the Reference API documents every public function with its
signature and parameters.
ffrprep through the CLI¶
As ffrprep is a BIDS-App , it is primarily designed as a command-line tool that you can run directly from your terminal or command prompt.
Ideally, using the provided Docker or Singularity images as they encapsulate all dependencies and ensure a consistent environment across different systems, ensuring ease-of-use and
reproducibility.
Downloading the example dataset¶
The download subcommand of the container fetches the example
OSF dataset used in the
Tutorial. The
container’s entrypoint dispatches download … to the
ffrprep-download console script bundled inside the image.
# Just the EEG data (default)
docker run --rm \
-v /path/to/data:/out:rw \
ksitek/ffrprep:latest \
download example --out /out
# Also fetch the stimulus audio + augment events.tsv with the
# BIDS stim_file column (needed for the stim-vs-response
# correlation in the analysis report)
docker run --rm \
-v /path/to/data:/out:rw \
ksitek/ffrprep:latest \
download example --with-stimuli --out /out
--with-stimuli is BooleanOptionalAction (default False);
--no-with-stimuli is its inverse. The download lands the EEG
data under <out>/ffrprep_raw_data/ and the stimulus files
under <out>/ffrprep_raw_data/stimuli/ per the BIDS spec, then
augments every sub-*/eeg/*_events.tsv with the matching
stim_file column based on a trial_type → filename lookup.
Command-Line Arguments¶
ffrprep: A BIDS-App for standardized FFR preprocessing and analysis
usage: ffrprep [-h] [-v]
[--participant_label PARTICIPANT_LABEL [PARTICIPANT_LABEL ...]]
[--stage {preprocessing,analysis,both}]
[--ref_channels REF_CHANNELS [REF_CHANNELS ...]]
[--high_pass HIGH_PASS] [--low_pass LOW_PASS] [--l_freq L_FREQ]
[--h_freq H_FREQ] [--no-filter] [--baseline START END]
[--picks PICKS] [--on-missing {warn,raise,ignore}]
[--event-id EVENT_ID] [--events-file EVENTS_FILE] [--tmin TMIN]
[--tmax TMAX] [--reject-eeg REJECT_EEG]
[--reject-mode {ptp,abs}] [--filter-method {fir,iir}]
[--no-auto-reject] [--concat-runs] [--save-each-node]
[--run RUN [RUN ...]] [--task TASK [TASK ...]]
[--split-by-trial-type | --no-split-by-trial-type]
[--trial-types TRIAL_TYPES [TRIAL_TYPES ...]]
[--difference-pairs DIFFERENCE_PAIRS [DIFFERENCE_PAIRS ...]]
[--response-window START END] [--f0 F0]
[--n-harmonics N_HARMONICS] [--harmonic-bin-hz HARMONIC_BIN_HZ]
[--harmonic-window START END] [--stimulus STIMULUS]
[--xcorr-stim-window START END] [--xcorr-resp-window START END]
[--xcorr-lag-range MIN_MS MAX_MS]
[--xcorr-lag-resp-window START END]
[--n-trials-presented N_TRIALS_PRESENTED]
[--min-usable-pct MIN_USABLE_PCT] [--min-snr MIN_SNR]
[--covariates COVARIATES [COVARIATES ...]]
[--skip_bids_validation] [--no-report] [--skip-existing]
[--clean-work-dir] [--keep-epochs | --no-keep-epochs]
[--n_procs N_PROCS] [--work_dir WORK_DIR]
bids_dir output_dir {participant,group}
Positional Arguments¶
- bids_dir
The directory with the input dataset formatted according to the BIDS standard.
- output_dir
The directory where the output files should be stored. If you are running group level analysis this folder should be prepopulated with the results of the participant level analysis.
- analysis_level
Possible choices: participant, group
Level of the analysis that will be performed. Multiple participant level analyses can be run independently (in parallel) using the same output_dir.
Named Arguments¶
- -v, --version
show program’s version number and exit
- --participant_label
The label(s) of the participant(s) that should be processed. The label corresponds to sub-<participant_label> from the BIDS spec (so it does not include “sub-“). Multiple participants can be specified with a space-separated list.
- --stage
Possible choices: preprocessing, analysis, both
Processing stage to run: preprocessing only, analysis only, or both stages sequentially.
- --skip_bids_validation
Assume the input dataset is BIDS compliant and skip the validation.
- --no-report
Skip HTML report generation (preprocessing and analysis). Derivatives are still written. Useful for bulk runs over many subjects, where report figures dominate runtime.
- --skip-existing
Skip (task, run) iterations whose outputs already exist (matched on the JSON sidecars, so this still works after –no-keep-epochs). Makes an interrupted run resumable by re-issuing the same command.
- --clean-work-dir
Delete each iteration’s nipype working files once it succeeds (they can be several GB per subject); worker logs are kept. Work dirs of failed iterations are left in place for debugging.
- --keep-epochs, --no-keep-epochs
Keep the large preprocessed epochs (_epo.fif) files after the analysis stage. –no-keep-epochs deletes them (JSON sidecars stay) once analysis and its report are done; ignored with –stage preprocessing because analysis still needs them.
- --n_procs
Number of parallel (task, run) workers per subject. Each worker runs its own preprocessing or analysis workflow with the Linear plugin. Memory footprint scales linearly with this value. A failure in any worker aborts the run. Default: 1 (sequential). For multi-node / cluster scaling, run one ffrprep invocation per subject (e.g. via slurm array or GNU parallel) and use –n_procs to control intra-subject parallelism.
- --work_dir
Path where intermediate results should be stored.
Preprocessing options¶
- --ref_channels
Reference channel(s) for re-referencing. Provide space-separated channel names (e.g. –ref_channels M1 M2). Use ‘average’ for average reference, or ‘skip’ if the data is already referenced and should not be re-referenced. Comma-separated single-argument styles are also accepted for backward compatibility (e.g. ‘M1,M2’).
- --high_pass
High-pass filter cutoff frequency in Hz.
- --low_pass
Low-pass filter cutoff frequency in Hz.
- --l_freq
Lower-pass edge in Hz (MNE name: l_freq). If provided, overrides –high_pass. Use ‘None’ via –no-filter to disable.
- --h_freq
Upper-pass edge in Hz (MNE name: h_freq). If provided, overrides –low_pass. Use ‘None’ via –no-filter to disable.
- --no-filter
Disable filtering entirely (equivalent to l_freq=None and h_freq=None). When set, any provided l_freq/h_freq/high_pass/low_pass are ignored.
- --baseline
Baseline correction period. Provide two numbers: START END in seconds (e.g. –baseline -0.2 0 for -200ms to 0ms).
- --picks
Channels to include in epochs. Comma-separated list or single channel name (e.g. ‘Cz’ or ‘Cz,Fz’). If not provided, all channels are considered.
- --on-missing
Possible choices: warn, raise, ignore
Behavior when events referenced by event_id are missing: warn, raise, or ignore. Matches MNE’s on_missing option.
- --event-id
Event id mapping. Provide a JSON string like ‘{“A”:1,”B”:2}’ or comma-separated pairs like ‘A:1,B:2’. If not provided, event ids will be inferred from events file or annotations.
- --events-file
Optional path to an events.tsv file to use for epoching. If not provided, events will be inferred from annotations.
- --tmin
Start time of epochs relative to event onset (s).
- --tmax
End time of epochs relative to event onset (s).
- --reject-eeg
Rejection threshold for EEG channels in Volts (peak-to-peak by default; see –reject-mode). Set to 0 to disable automatic rejection.
- --reject-mode
Possible choices: ptp, abs
How –reject-eeg is applied: ‘ptp’ = MNE peak-to-peak criterion (default); ‘abs’ = drop epochs whose absolute amplitude reaches the threshold at any sample (max|x| >= threshold), e.g. –reject-mode abs –reject-eeg 35e-6.
- --filter-method
Possible choices: fir, iir
Band-pass filter design: ‘fir’ = MNE zero-phase FIR with automatic transition bandwidth (default); ‘iir’ = zero-phase first-order Butterworth high-pass then low-pass (12 dB/octave overall, forward-backward), as used in some published FFR pipelines.
- --no-auto-reject
Disable automatic amplitude-based epoch rejection.
- --concat-runs
Concatenate multiple runs for a subject and process them as a single recording. By default runs are processed separately.
- --save-each-node
Write intermediate outputs to disk after each preprocessing step (disk-backed mode). This reduces peak memory at the expense of increased I/O and runtime.
- --run
Run label(s) to process for the participant (without the ‘run-’ prefix). If not provided, all runs found for the subject will be processed. Multiple runs can be provided as space-separated values, or as a single comma-separated string (e.g. ‘1 2’ or ‘1,2’).
- --task
Task label(s) to process for the participant (without the ‘task-’ prefix). If not provided, all tasks found for the subject will be processed. Multiple tasks can be provided as space-separated values, or as a single comma-separated string (e.g. ‘active passive’).
Analysis options¶
- --split-by-trial-type, --no-split-by-trial-type
Emit per-trial-type epoched and evoked outputs. Pass –no-split-by-trial-type to fall back to a single combined output per (subject, task, run).
- --trial-types
Restrict per-trial-type outputs to this subset of trial types (matched against the events.tsv
trial_typecolumn). If omitted, all trial types are emitted.- --difference-pairs
Difference evokeds to compute, given as
A:Btokens (e.g. Pos:Neg). For exactly two trial types the diff is auto-computed; this flag is required to opt in to diffs when there are three or more trial types.- --response-window
Response window for RMS SNR and band-power summary metrics. Provide two numbers: START END in seconds, relative to the Evoked time axis (e.g. –response-window 0.1 0.2 for 100-200 ms).
Group-level options¶
Used with the ‘group’ analysis level. They add optional columns to the group metrics table; nothing here performs statistical inference.
- --f0
Stimulus fundamental frequency in Hz. When set, the group metrics table gains rms_snr_polarity_sum, f0_uv and upper_harmonics_uv (band-averaged FFT amplitude at each harmonic, in microvolts) computed on the SUM of the two per-trial-type averages.
- --n-harmonics
Number of harmonics (including the fundamental) for –f0.
- --harmonic-bin-hz
Full width in Hz of the band averaged around each harmonic.
- --harmonic-window
Response window in seconds for the harmonic FFT.
- --stimulus
WAV file of the stimulus (t=0 at stimulus onset). When set, the group metrics table gains stim2resp_r / stim2resp_z / stim2resp_lag_ms: the maximum stimulus-to-response cross-correlation (‘coeff’ normalization, Fisher z) on the polarity-summed response. The stimulus is resampled to the EEG sampling rate.
- --xcorr-stim-window
Stimulus segment in seconds for –stimulus.
- --xcorr-resp-window
Response segment in seconds for –stimulus.
- --xcorr-lag-range
Additionally report the maximum correlation restricted to this lag window in ms (columns stim2resp_lim_*). Positive lag = response delayed relative to the stimulus, with both segments aligned at their start.
- --xcorr-lag-resp-window
Response segment in seconds for the lag-restricted correlation (defaults to –xcorr-resp-window).
- --n-trials-presented
Trials presented per recording; adds usable_pct (kept trials / presented) to the group metrics table.
- --min-usable-pct
QC threshold on usable_pct (needs –n-trials-presented). Adds qc_usable_pct_ok, qc_include and qc_reason columns; flagged subjects are kept in the table and grand averages.
- --min-snr
QC threshold on RMS SNR (polarity-sum when available). Adds qc_snr_ok, qc_include and qc_reason columns; flagged subjects are kept in the table and grand averages.
- --covariates
Extra subject-level TSV files (with a participant_id column) to join onto the group metrics table, in addition to <bids_dir>/participants.tsv.
Example Call(s)¶
The examples below all use Docker — the recommended path. They
build up from the simplest call (preprocessing only, one subject)
to per-trial-type analysis outputs with explicit difference pairs.
Singularity users can swap the docker run … invocation for
singularity run --cleanenv -B <host>:<container> <image.sif>
keeping every flag below the image name identical.
All examples assume your BIDS dataset is at
/local/bids_dataset on the host and the derivatives should
land in /local/bids_dataset/derivatives.
Example 1 - Basic preprocessing¶
docker run --rm \
-v /local/bids_dataset:/data:rw \
ksitek/ffrprep:latest \
/data \
/data/derivatives \
participant \
--participant_label 01 02 \
--stage preprocessing
What’s in this call:
docker run --rmruns the container and removes it after completion.-v /local/bids_dataset:/data:rwmounts the local BIDS dataset read-write so derivatives can be written back to disk.ksitek/ffrprep:latestis the published image (see Installation for tag pinning)./data(1st positional) is the BIDS dataset inside the container;/data/derivatives(2nd) is the output directory;participant(3rd) is the BIDS-App analysis level.--participant_label 01 02processes onlysub-01andsub-02.--stage preprocessingruns only the preprocessing stage.
Example 2 - Full analysis with custom parameters¶
docker run --rm \
-v /local/bids_dataset:/data:rw \
ksitek/ffrprep:latest \
/data \
/data/derivatives \
participant \
--stage both \
--high_pass 0.5 \
--low_pass 50.0 \
--ref_channels average \
--baseline "-0.1,0" \
--n_procs 4
What’s in this call:
--stage bothruns both preprocessing and analysis stages.--high_pass 0.5/--low_pass 50.0set the band-pass filter in Hz.--ref_channels averageuses an average reference.--baseline "-0.1,0"sets the baseline window to −100 ms → 0 ms.--n_procs 4runs 4 per-(task, run) iterations in parallel per subject (see Parallelization below).
By default the analysis stage emits one evoked response per
trial_type value in events.tsv
(_desc-evoked{Cond}.fif), a combined evoked across all events
(_desc-evoked.fif), and — for 2-trial-type datasets — an
auto-paired difference evoked (_desc-evokedDiff{A}Vs{B}.fif).
This per-trial-type split is governed by --split-by-trial-type
(default on); pass --no-split-by-trial-type to emit only the
combined evoked. Examples 3 and 4 below show how to pick explicit
difference pairs or restrict the output set.
Example 3 - Per-trial-type analysis with explicit difference pairs¶
For datasets with more than two trial_type values, the
automatic difference is ambiguous; use
--difference-pairs A:B [C:D …] to specify which pairs to
subtract. Each pair becomes one
_desc-evokedDiff{A}Vs{B}.fif file. The per-trial-type
_desc-evoked{Cond}.fif and combined _desc-evoked.fif
files are always emitted.
docker run --rm \
-v /local/bids_dataset:/data:rw \
ksitek/ffrprep:latest \
/data \
/data/derivatives \
participant \
--stage both \
--difference-pairs positive:negative tone1:tone2 \
--n_procs 4
What’s in this call:
--difference-pairs positive:negative tone1:tone2emits two difference evokeds:_desc-evokedDiffPositiveVsNegative.fif(positive − negative) and_desc-evokedDiffTone1VsTone2.fif(tone1 − tone2).All per-trial-type and combined evokeds for this (task, run) group are also written —
--split-by-trial-typeis on by default, and--difference-pairsrequires it (the pairs are subtracted from the per-trial-type evokeds, so combining it with--no-split-by-trial-typewould leave nothing to subtract).
Example 4 - Restrict outputs to a subset, no split¶
The trial-type split is controlled by a single boolean flag, exposed
in both forms via argparse.BooleanOptionalAction:
--split-by-trial-type(the default) emits the combined evoked plus one_desc-evoked{Cond}.fifper trial type plus the auto-paired / explicit difference evokeds. Passing this flag explicitly is equivalent to omitting it.--no-split-by-trial-typestrips back to the combined evoked only — no per-trial-type files, no differences.
To keep the split on but restrict it to a subset of trial types
(while still emitting the combined evoked), pass
--trial-types A B.
# Combined evoked only — strips per-trial-type files and
# any (auto-paired or explicit) difference evokeds
docker run --rm \
-v /local/bids_dataset:/data:rw \
ksitek/ffrprep:latest \
/data \
/data/derivatives \
participant \
--stage both \
--no-split-by-trial-type \
--n_procs 4
# Only emit per-trial-type files for "positive" + the combined
# evoked; drop the "negative" output even though events.tsv
# contains both
docker run --rm \
-v /local/bids_dataset:/data:rw \
ksitek/ffrprep:latest \
/data \
/data/derivatives \
participant \
--stage both \
--trial-types positive \
--n_procs 4
Example 5 - Group-level aggregation¶
Once participant-level ffrprep has been run for two or more subjects
into the same output_dir, running the group analysis level
against that same output_dir aggregates their derivatives — it
does not read bids_dir or re-run any preprocessing/analysis.
docker run --rm \
-v /local/bids_dataset:/data:rw \
ksitek/ffrprep:latest \
/data \
/data/derivatives \
group
For each (task, session, run) with at least two contributing
subjects, this writes a grand-average evoked response, a per-subject
scalar-metrics table (RMS SNR, band power, response consistency,
recomputed from each subject’s saved evoked/epochs), and a single
group_report.html, all under
/data/derivatives/ffrprep-group/. --participant_label and
--task restrict which subjects/tasks are aggregated, same as at
the participant level.
Optional metrics for FFR studies are switched on with flags. With
--f0 the metrics table gains f0_uv and upper_harmonics_uv (FFT
amplitude, in microvolts, averaged in a 60 Hz band around each harmonic of
the stimulus F0 over 60-180 ms) and rms_snr_polarity_sum, all computed
on the sum of the two per-trial-type averages. With --stimulus (a
WAV file) it also gains stim2resp_r / stim2resp_z /
stim2resp_lag_ms, the maximum stimulus-to-response cross-correlation
(xcorr “coeff” normalization, Fisher z). participants.tsv from the
BIDS directory (plus any --covariates TSVs) is joined onto the saved
table so downstream statistics have group, age, etc. alongside the metrics:
ffrprep /data /data/derivatives group \
--f0 100 --n-harmonics 10 --harmonic-window 0.06 0.18 \
--stimulus /path/to/da.wav --xcorr-stim-window 0.05 0.17 \
--xcorr-resp-window 0.06 0.18 --n-trials-presented 6000
--min-usable-pct and --min-snr add quality-control flag columns
(qc_usable_pct_ok, qc_snr_ok, qc_include and a qc_reason text
column) to the table. Subjects below a threshold are flagged, not removed:
the table and the grand averages still contain them, and you decide
downstream whether to filter on qc_include. --min-usable-pct needs
--n-trials-presented; --min-snr uses rms_snr_polarity_sum when
--f0 is set and the combined-evoked rms_snr otherwise.
Next to *_metrics.tsv the group step writes *_metrics.json, a
BIDS-style data dictionary with a description and units for every column. It
records the windows, harmonic settings and QC thresholds the run used, and
copies the descriptions of joined covariates from the .json next to their
TSV (for example participants.json) when there is one.
This step only aggregates outputs participant-level ffrprep has already computed — it does not perform any group-level statistics (no hypothesis tests, no GLM). Downstream statistical analysis is left to the user, using their tool of choice (e.g. MNE-Python directly on the saved grand-average/per-subject derivatives).
Parallelization and cluster usage¶
ffrprep follows the standard BIDS-App parallelism model:
Inside one invocation —
--n_procs Nruns N per-(task, run) iterations concurrently for the subject(s) being processed. Each worker runs its own preprocessing or analysis workflow with the NipypeLinearplugin. Default:--n_procs 1(sequential). Memory footprint scales linearly with N — each worker loads its own raw + epochs into memory, so dial it down on small machines.Across invocations — for multi-node / cluster scaling, run one
ffrprepinvocation per subject under your scheduler (slurm job array, GNU parallel, HTCondor, etc.). Both layers compose: 8 parallel slurm tasks each with--n_procs 4gives 32-way effective parallelism.
Single workstation¶
ffrprep \
/data/bids_dataset \
/data/bids_dataset/derivatives \
participant \
--participant_label 01 02 03 \
--n_procs 4
The 4 workers chew through each subject’s (task, run) iterations in parallel, then move to the next subject.
Cluster (slurm job array)¶
Run one invocation per subject via the scheduler; each job uses
--n_procs for intra-subject parallelism. Example wrapper script:
# ffrprep_one_subject.sh
#!/bin/bash
#SBATCH --array=0-99
#SBATCH --cpus-per-task=4
#SBATCH --mem=16G
SUBJECTS=(sub-01 sub-02 sub-03 ...)
SUB=${SUBJECTS[$SLURM_ARRAY_TASK_ID]}
ffrprep /data /data/derivatives participant \
--participant_label ${SUB#sub-} \
--n_procs $SLURM_CPUS_PER_TASK
Cluster (GNU parallel on a single beefy box)¶
parallel -j 8 \
"ffrprep /data /data/derivatives participant \
--participant_label {} --n_procs 4" \
::: 01 02 03 04 05 06 07 08
Resuming and saving disk space¶
Large cohorts are limited by disk space more than by CPU: each subject’s Nipype working directory can hold several GB, and the epochs files are much larger than the evokeds. Three flags make bulk runs resumable and lean:
ffrprep /data /data/derivatives participant \
--participant_label 01 02 03 \
--skip-existing --clean-work-dir --no-keep-epochs
--skip-existingskips (task, run) iterations whose JSON sidecar outputs already exist, so an interrupted run can simply be started again. Completion is judged from the sidecars, so it still works after--no-keep-epochs. Iterations that are already complete build no report.--clean-work-dirdeletes each iteration’s Nipype working files once it has succeeded. Worker logs are kept, and the working files of a failed iteration are kept for debugging.--no-keep-epochsdeletes the*_epo.fiffiles after the analysis stage (evokeds and all JSON sidecars stay). It is ignored, with a warning, for--stage preprocessingbecause the analysis stage still needs the epochs.
Failure handling¶
A failure in any (task, run) iteration aborts the run (fail-fast). The
exception propagates up from the worker to the CLI entry point, so the
underlying error is visible in the terminal output. Per-iteration logs
land in <work_dir>/<task>-<run>.log so you can drill into the
failing iteration without scanning the whole subject log.
Support and communication¶
The documentation of this project is found here: https://sitek.github.io/ffrprep.
All bugs, concerns and enhancement requests for this software can be submitted here: https://github.com/sitek/ffrprep/issues.
If you have a problem or would like to ask a question about how to use ffrprep,
please submit a question to NeuroStars.org with an ffrprep tag.
NeuroStars.org is a platform similar to StackOverflow but dedicated to neuroinformatics.
All previous ffrprep questions are available here:
http://neurostars.org/tags/ffrprep/
Not running on a local machine? - Data transfer¶
Please contact you local system administrator regarding possible and favourable transfer options (e.g., rsync or FileZilla).
A very comprehensive approach would be Datalad, which will handle data transfers with the appropriate settings and commands. Datalad also performs version control over your data.