# Advanced NeuroVec Patterns

#### 2026-04-16

This article is now the deeper companion to `vignette("VolumesAndVectors")`. Read that first if you want the compact introduction to `NeuroVec`. Stay here when you want more detail on multi-file reads, manual construction, matrix views, and dense/sparse conversions for 4D data.

## Working with neuroimaging time-series data

This vignette focuses on the parts of the 4D workflow that remain useful after you already understand the basic container model:

- reading many 4D files into a `NeuroVecSeq`
- creating `NeuroVec` objects from arrays or matrices
- time-series transforms and concatenation
- dense/sparse conversion patterns
- writing vectors back to disk

### Reading multiple four-dimensional images

    file_name <- system.file("extdata", "global_mask_v4.nii", package = "neuroim2")
    vec <- read_vec(file_name)
    vec_multi <- read_vec(c(file_name, file_name, file_name))
    dim(vec_multi)
    #> [1] 64 64 25 12

    vec2 <- read_vec(rep(file_name, 10))
    vec2
    #> <NeuroVecSeq> [10 vectors]
    #> ── Sequence ────────────────────────────────────────────────────────────────────
    #>     [1]         : DenseNeuroVec 64x64x25 x 4t
    #>     [2]         : DenseNeuroVec 64x64x25 x 4t
    #>     [3]         : DenseNeuroVec 64x64x25 x 4t
    #>     [4]         : DenseNeuroVec 64x64x25 x 4t
    #>     [5]         : DenseNeuroVec 64x64x25 x 4t
    #>   ... and 5 more

### Creating a NeuroVec from an in-memory array

You can build a `NeuroVec` directly from arrays or matrices:

    set.seed(1)
    dims <- c(24, 24, 24, 5)
    arr  <- array(rnorm(prod(dims)), dims)
    sp4  <- NeuroSpace(dims, spacing = c(2,2,2))
    dvec <- NeuroVec(arr, sp4)
    dim(dvec)
    #> [1] 24 24 24  5

You can also start from a matrix (voxels × time or time × voxels) using `DenseNeuroVec`:

    mat  <- matrix(rnorm(prod(dims)), nrow = prod(dims[1:3]), ncol = dims[4])
    dvec2 <- DenseNeuroVec(mat, sp4)
    all.equal(dim(dvec), dim(dvec2))
    #> [1] TRUE

### Time-series transforms: z-scoring and summary volumes

Z-score each voxel’s time-series (center and scale across time):

    vec_z <- scale_series(dvec, center = TRUE, scale = TRUE)
    dim(vec_z)
    #> [1] 24 24 24  5

Compute a mean volume across time and return a 3D `NeuroVol`:

    M      <- as.matrix(dvec)              # voxels × time
    vmean  <- rowMeans(M)                  # per-voxel mean
    mean3d <- NeuroVol(vmean, drop_dim(space(dvec)))
    mean3d
    #> <DenseNeuroVol> [114.6 Kb]
    #> ── Spatial ─────────────────────────────────────────────────────────────────────
    #>   Dimensions    : 24 x 24 x 24
    #>   Spacing       : 2 x 2 x 2 mm
    #>   Origin        : 0, 0, 0
    #>   Orientation   : RAS
    #> ── Data ────────────────────────────────────────────────────────────────────────
    #>   Range         : [-1.643, 1.756]

### Concatenating along time

Append time points by concatenating vectors or volumes:

    dvec_more <- concat(dvec, dvec)        # doubles the time dimension
    dim(dvec_more)
    #> [1] 24 24 24 10

### Dense ↔︎ sparse workflows

Sparse representations store only voxels within a mask. This is handy for large ROIs or brain masks.

    # Build a random mask and convert a dense vec to sparse
    mask_arr <- array(runif(prod(dims[1:3])) > 0.7, dims[1:3])
    mask_vol <- LogicalNeuroVol(mask_arr, drop_dim(sp4))

    svec <- as.sparse(dvec, mask_vol)     # SparseNeuroVec with explicit mask
    svec                                 # note the stored mask and cardinality
    #> <SparseNeuroVec> [306.2 Kb]
    #> ── Spatial ─────────────────────────────────────────────────────────────────────
    #>   Dimensions    : 24 x 24 x 24
    #>   Time Points   : 5
    #>   Spacing       : 2 x 2 x 2
    #>   Origin        : 0, 0, 0
    #> ── Sparse ──────────────────────────────────────────────────────────────────────
    #>   Cardinality   : 4128

    # Convert back to dense if needed
    dense_again <- as.dense(svec)
    all.equal(dim(dense_again), dim(dvec))
    #> [1] TRUE

Tip: For file-backed or memory-mapped vectors, convert to `DenseNeuroVec` via a matrix if you need dense-only operations:

    dv_dense <- DenseNeuroVec(as.matrix(vec), space(vec))

### Writing vectors to disk

You can write `NeuroVec` and `NeuroVol` objects as NIfTI files:

    tmp_vec <- tempfile(fileext = ".nii.gz")
    write_vec(sub_vector(vec, 1:3), tmp_vec)
    file.exists(tmp_vec)
    #> [1] TRUE
    unlink(tmp_vec)

### Putting it together with an ROI

Combine ROI extraction with time-series transforms:

    vol3d <- read_vol(system.file("extdata", "global_mask_v4.nii", package="neuroim2"))
    roi   <- spherical_roi(vol3d, c(12,12,12), radius = 6)
    rts   <- series_roi(vec, roi)          # ROIVec (T × N with coords)

    # z-score each column (voxel) across time, then average within ROI
    mat_roi  <- values(rts)                # T × N
    mat_z    <- base::scale(mat_roi, center=TRUE, scale=TRUE)
    roi_mean <- rowMeans(mat_z)
    length(roi_mean)                       # matches time dimension
    #> [1] 4

This article now complements the core path rather than replacing it. Use `vignette("VolumesAndVectors")` for the basic 4D container model and come back here when you need denser transformation and conversion patterns.