Reading fmristore HDF5 files

When do you need this?

Many fMRI analysis pipelines store first-level results in HDF5 files using the fmristore format. These files can contain voxelwise statistical maps (labeled volumes), parcel-level timeseries, or latent-space decompositions. If you want to run group-level analyses on data already stored in fmristore, fmrigds reads it directly—auto-detecting the layout and converting to the GDS structure.

All examples below use eval = FALSE because they require HDF5 files that are not shipped with the package. To run them locally, you need hdf5r:

install.packages("hdf5r")

Two paths for reading data

fmrigds supports two kinds of HDF5 input. The path you use depends on how the file was created:

Path B (recommended): Native /gds format
Files written by write_out(..., format = "h5") contain a /gds group with full metadata, provenance, and structure. These round-trip perfectly.
Path A (legacy): Original fmristore layouts
Files created by the fmristore package use one of three layout types: LabeledVolumeSet, Parcellated, or LatentNeuroVec. fmrigds auto-detects the layout and converts to GDS.

If you control file creation, use Path B. If you’re reading existing fmristore files, Path A works automatically with gds(path).

Native /gds layout (Path B)

The simplest case—files with a /gds group are read directly with full fidelity:

library(fmrigds)

plan <- gds("group_store.h5")
g <- compute(plan)

# Everything is preserved as written
names(assays(g))
space(g)
metadata(g)$provenance

Create files in this format with write_out():

gds(source) |>
  reduce(method = "fixed") |>
  write_out("group_results.h5", format = "h5") |>
  compute()

Labeled volumes (LabeledVolumeSet)

The most common fmristore layout for first-level outputs. It stores statistical maps in voxel space with one dataset per contrast.

Structure inside the HDF5 file:

/mask         3D binary mask (which voxels have data)
/data/
  cope1       Packed vector of values for masked voxels
  cope2       ...
/labels       List of contrast names
plan <- gds("subject01_lvs.h5")
g <- compute(plan)

space(g)               # voxel space
names(assays(g))       # "beta", "var", etc.
contrasts(g)           # e.g., c("cope1", "cope2")
dim(assay(g, "beta"))  # [n_voxels x n_subjects x n_contrasts]

Contrast names containing special characters are sanitized (e.g., "cope-1" becomes "cope_1").

Parcellated data (H5ParcellatedScan)

Parcellated layouts store timeseries data for brain parcels (ROIs). Each file contains:

/cluster_map    3D integer array (voxels -> parcel IDs)
/scans/
  001           Matrix: parcels x timepoints
  002           ...

The challenge: fmristore stores timeseries, but GDS needs contrasts. You must tell fmrigds how to convert timepoints into contrasts using a temporal policy.

Temporal policies

"as_is" (default) — treat each timepoint as a separate contrast:

plan <- gds("parcellated.h5")
g <- compute(plan)
contrasts(g)  # c("t_001", "t_002", "t_003", ...)

Use this when you want to analyse the full timecourse.

"mean" — average all timepoints into one contrast:

plan <- gds("parcellated.h5", temporal_policy = "mean")
g <- compute(plan)
contrasts(g)  # "mean"

Only beta is averaged; var/se become NA (no uncertainty is fabricated).

"design" — apply a contrast matrix to extract specific effects:

# Extract two contrasts from 3 timepoints
C <- cbind(early = c(1, 0, 0), late = c(0, 1, 0))

plan <- gds(
  "parcellated.h5",
  temporal_policy = "design",
  contrast_matrix = C,
  contrast_names = c("early", "late")
)

g <- compute(plan)
contrasts(g)           # c("early", "late")
dim(assay(g, "beta"))  # [n_parcels x n_subjects x 2]

Variance propagation: if the source file includes /var, fmrigds applies Var(C'y) = (C^2)' Var(y) to propagate uncertainty through the linear combination.

Space type

After reading, the GDS has parcel-type space:

g <- compute(gds("parcellated.h5"))
space(g)  # parcels space with labels

Latent bases (LatentNeuroVec)

Latent layouts store dimensionality-reduced data where brain activity is represented by a small number of spatial components (e.g., from PCA or ICA).

Structure:

/basis          Matrix: components x voxels
/scans/
  001           Embeddings: how much each component is expressed over time
  002           ...
plan <- gds("latent_small.h5")
g <- compute(plan)

space(g)               # basis space
dim(assay(g, "beta"))  # [k_components x n_subjects x n_contrasts]

Each “sample” is a spatial component (a brain network). If the original data had no uncertainty estimates, var/se will be NA.

Reading subsets efficiently

For large datasets, read specific slices using the block parameter instead of loading everything into memory:

# Read only first 10 samples and first contrast
raw <- compute(
  plan,
  assays = "beta",
  block = list(sample = 1:10, contrast = 1)
)

# Read specific subjects
raw <- compute(
  plan,
  assays = c("beta", "var"),
  block = list(subject = c("sub-01", "sub-03"))
)

Provenance tracking

Every compute() operation records what was done:

plan <- gds("data.h5") |>
  reduce(method = "fixed")

g <- compute(plan)
metadata(g)$provenance$log

For native /gds files (Path B), provenance is preserved across write–read cycles.

Quick reference

Scenario What to do
Creating new files write_out(..., format = "h5") (Path B)
Existing fmristore files gds(path) — layout auto-detected
Parcellated timeseries Set temporal_policy ("as_is", "mean", or "design")
Large files Use block = list(...) to read subsets
Reproducibility Provenance tracked automatically in metadata()$provenance

Next steps

  • vignette("fmrigds") — getting started with the core pipeline
  • vignette("spatial-operations") — masking, alignment, and space transformations
  • vignette("as-plan-and-spatial-fdr") — post-hoc corrections and spatial FDR
  • ?gds for the full list of adapter arguments