Skip to contents

Reusable targets pipeline factories for the Physio ecosystem. Each factory returns a list of targets, so a whole reproducible _targets.R for a standard analysis is a couple of lines — with incremental re-runs, dependency tracking, and byte-level provenance for free.

library(targets)
library(PhysioPipe)

# A factory returns ready-made `targets` objects -- no _targets.R or pipeline
# run is needed to see what it produces.
hrv <- tar_ecg_hrv()                            # ECG -> R-peaks -> RR -> HRV + tachogram
length(hrv)                                     # the linked targets it generates
vapply(hrv, function(t) t$name, character(1))   # ecg_pe, ecg_peaks, ... ecg_hrv

# A whole standard pipeline is just a list of factories:
pipeline <- list(
  tar_ecg_hrv(),        # resting ECG -> R-peaks -> RR -> HRV (time + freq) + figure
  tar_eeg_bandpower(),  # EEG -> band power per channel
  tar_emg_features(),   # EMG -> linear envelope + spectral fatigue
  tar_eda_scr()         # EDA -> tonic/phasic -> SCR features
)

Save that pipeline list as _targets.R, then build it from the project directory. Building runs the modality packages (PhysioIO/PhysioECG/PhysioEDA/…), which are optional dependencies installed only for the stages you use:

targets::tar_make()             # build (skips unchanged steps)
targets::tar_visnetwork()       # view the DAG (needs visNetwork)
targets::tar_read(ecg_hrv)      # a result

Out of the box every factory runs on bundled/synthetic demo data (ECG uses the MIT-BIH 100 excerpt in PhysioIO; EEG/EMG/EDA use a seeded synthetic signal from pp_simulate()). Point them at real data with records= / source=.

The collection

Factory Pipeline Terminal result
tar_ecg_hrv() read → lead → R-peaks → RR → HRV <name>_hrv (+ tachogram)
tar_eeg_bandpower() signal → Welch PSD band power <name>_power
tar_emg_features() signal → envelope / spectral fatigue <name>_envelope, <name>_fatigue
tar_eda_scr() signal → tonic/phasic → SCR features <name>_features
tar_physio_report() targets → Quarto report report (needs tarchetypes+quarto)

Every factory takes a name prefix, so several can coexist in one project (tar_ecg_hrv("subjA"), tar_ecg_hrv("subjB")).

Real data

Reading a recording needs the modality package that owns that format – PhysioIO for EDF and WFDB – which is a Suggests, so install it alongside:

# EEG from an EDF file (needs PhysioIO)
tar_eeg_bandpower(source = quote(PhysioIO::readEDF("sub01.edf")))

# ECG cohort: multiple WFDB records -> dynamic branching + a combined table
tar_ecg_hrv(records = c("data/subjA", "data/subjB", "data/subjC"))
#   -> ecg_pe, ecg_lead, ... branch per record; ecg_cohort row-binds them.

Adding a new pipeline (extending the collection)

A factory is just a function returning targets::tar_target_raw() objects with a prefixed name. Copy R/tar-modality.R as a template, wire the ecosystem functions with bquote(), and add a tar_dir() test in tests/testthat/test-factories.R.

How this fits the ecosystem

  • PhysioPipe = reusable pipelines (infrastructure).
  • PhysioRecipes = finished case studies — a recipe is a PhysioPipe factory pointed at a frozen public dataset plus a narrative.
  • PhysioLake = the lineage/artifact sink for pipeline runs.

Orchestrator-agnostic (CLI · Parquet · Nextflow)

The targets factory is one binding. The actual analysis logic is exposed as a CLI and writes language-neutral Parquet, so the same step runs under targets, Nextflow, or plain shell — and the output is readable from Python/DuckDB.

# Layer 2 — orchestrator-agnostic CLI (writes Parquet)
Rscript $(Rscript -e 'cat(system.file("cli/physio-ecg-hrv.R", package="PhysioPipe"))') \
    --record data/100 --channel 1 --out results/100_hrv.parquet

# Layer 3b — Nextflow calls the same CLI (container-ready for HPC/cloud)
nextflow run $(Rscript -e 'cat(system.file("nextflow/ecg_hrv.nf", package="PhysioPipe"))') \
    --record data/100 --outdir results
# Layer 3a — targets emits the same Parquet as a tracked target (needs a built
# pipeline and the arrow package)
tar_read(ecg_parquet)   # "results/ecg_hrv.parquet"

Verified equivalent: CLI, targets, and Nextflow all produce mean_hr = 72.03, sdnn = 1.389 on the bundled MIT-BIH 100 record; the Parquet reads back in Python with no R runtime.

Install

# the containers build on Bioconductor, so its repositories are needed too
install.packages("BiocManager", repos = "https://cloud.r-project.org")
install.packages("PhysioPipe",
  repos = c("https://x-biosignal.r-universe.dev", BiocManager::repositories()))