PhysioIO provides two complementary features for managing collections of physiological recordings:
This vignette demonstrates both capabilities and shows how they work together.
The Brain Imaging Data Structure (BIDS) is a standard for organizing neuroimaging data into a consistent directory layout with machine-readable metadata files. PhysioIO supports the BIDS-EEG and BIDS-iEEG extensions.
A minimal BIDS dataset looks like this:
my_dataset/
dataset_description.json
sub-01/
eeg/
sub-01_task-rest_eeg.edf
sub-01_task-rest_eeg.json
sub-01_task-rest_channels.tsv
sub-01_task-rest_events.tsv
sub-01_task-rest_electrodes.tsv
Reference: Gorgolewski KJ, et al. (2016). “The brain imaging data structure, a format for organizing and describing outputs of neuroimaging experiments.” Scientific Data, 3, 160044.
library(PhysioIO)
# Read a single recording
pe <- readBIDS("path/to/bids", subject = "01", task = "rest")
# With session and run
pe <- readBIDS(
"path/to/bids",
subject = "01",
session = "baseline",
task = "oddball",
run = 1,
modality = "eeg"
)
# Skip loading events
pe <- readBIDS("path/to/bids", subject = "02", task = "rest",
load_events = FALSE)readBIDS() automatically loads the EDF data file and
merges information from the companion _channels.tsv,
_events.tsv, and _electrodes.tsv sidecar files
when they are present.
# Export a PhysioExperiment to BIDS layout
writeBIDS(pe, "output/bids", subject = "01", task = "rest")
# With session and run identifiers
writeBIDS(pe, "output/bids",
subject = "01", session = "baseline",
task = "oddball", run = 1)writeBIDS() creates the required directory structure,
writes the EDF data file, and generates the sidecar TSV and JSON
files.
result <- validateBIDS("path/to/bids")
if (result$valid) {
message("Dataset is BIDS-compliant")
message("Subjects found: ", result$n_subjects)
} else {
message("Errors: ", paste(result$errors, collapse = "; "))
message("Warnings: ", paste(result$warnings, collapse = "; "))
}The validator checks for dataset_description.json,
subject directory naming, modality directories, and required metadata
fields.
When a study involves dozens or hundreds of recording sessions, it becomes impractical to load every file just to find which subjects had a particular task or which recordings fall within a date range. The DuckDB backend lets you register experiment metadata once and query it efficiently.
Reference: Raasveldt M, Muehleisen H (2019). “DuckDB: an embeddable analytical database.” Proceedings of the 2019 International Conference on Management of Data (SIGMOD).
initPhysioSchema() creates the following tables:
| Table | Purpose |
|---|---|
experiments |
One row per recording session (subject, task, sampling rate, etc.) |
channels |
Channel metadata (label, type, unit, electrode position) |
events |
Experimental events (onset, duration, type) |
signal_chunks |
Optional chunked signal data stored as BLOBs |
epochs |
Epoched data metadata |
annotations |
Free-form annotations |
# Read a recording
pe <- readEDF("sub01_rest.edf")
# Register metadata only
exp_id <- registerExperiment(con, pe,
subject_id = "sub-01",
task = "rest"
)
# Register with signal data stored in the database
exp_id <- registerExperiment(con, pe,
subject_id = "sub-01",
task = "rest",
store_signals = TRUE,
chunk_size = 10000L
)# Get database statistics
stats <- dbStats(con)
cat("Experiments:", stats$n_experiments, "\n")
cat("Channels:", stats$n_channels, "\n")
cat("Events:", stats$n_events, "\n")
cat("Subjects:", paste(stats$subjects, collapse = ", "), "\n")
# Delete an experiment and all related records
deleteExperiment(con, exp_id)A common workflow is to organize raw data in BIDS format on disk and register each recording in a DuckDB database for fast metadata queries.
# Set up database
con <- connectDatabase("study.duckdb")
initPhysioSchema(con)
# Iterate over BIDS subjects
bids_root <- "path/to/bids"
subjects <- listBIDSSubjects(bids_root)
for (subj in subjects) {
# Read from BIDS
pe <- readBIDS(bids_root, subject = subj, task = "rest")
# Register in database
registerExperiment(con, pe,
subject_id = paste0("sub-", subj),
task = "rest"
)
}
# Now query across all subjects
all_rest <- queryExperiments(con, task = "rest")
print(all_rest)
disconnectDatabase(con)