A LatentNeuroVec behaves like a neuroimaging time
series, but it stores a factorization instead of a dense time-by-voxel
matrix. This vignette shows the basic access pattern: encode data,
inspect the factors, pull out a voxel time series, and reconstruct only
when you need the dense matrix.
The examples use a small 4 x 4 x 4 mask and a 24-time-point matrix. Rows are time points and columns are in-mask voxels.
A temporal DCT basis stores shared time functions in
basis() and voxel weights in loadings().
lat <- encode(X, spec_time_dct(k = 8), mask = mask_vol, materialize = "matrix")
data.frame(
time_points = nrow(basis(lat)),
components = ncol(basis(lat)),
voxels = nrow(loadings(lat))
)
#> time_points components voxels
#> 1 24 8 64The dense reconstruction has the same shape as the input, but it is lossy because eight DCT components cannot represent every 24-point time course.
Use series() when you want a time course for one voxel
or a small group of voxels without thinking about the full matrix.
voxel_one <- series(lat, 1L)
head(voxel_one)
#> [1] 0.9893044 0.3916249 -0.4362176 -1.0104257 -1.0466904 -0.6113938Coordinate access is available when you know the voxel location in the image space.
A LatentNeuroVec behaves like the 4D
(x, y, z, time) array it stands in for, so the usual
neuroim2 indexing verbs work — each one reconstructs only
the slice you ask for.
Use [[ to pull one time point as a 3D volume:
vol1 <- lat[[1]]
class(vol1)
#> [1] "SparseNeuroVol"
#> attr(,"package")
#> [1] "neuroim2"
dim(vol1)
#> [1] 4 4 4Use [ with four indices to extract a spatial-by-time
sub-array:
You never materialize the full dense array to get a corner of it; the slice is reconstructed on demand from the factors.
The default handle mode stores the basis as a lightweight
BasisHandle reference instead of a dense matrix, and only
materializes it the first time you ask for it. Matrix mode materializes
up front, which is useful when you plan to reconstruct or access many
series repeatedly.
To see the difference you have to look at the stored slot directly:
basis() materializes a handle on access, so it
always hands back a dense matrix in either mode. The slot is where the
two representations diverge.
lat_handle <- encode(X, spec_time_dct(k = 8), mask = mask_vol,
materialize = "handle")
data.frame(
eager_slot = class(lat@basis)[1],
handle_slot = class(lat_handle@basis)[1],
same_reconstruction = isTRUE(all.equal(as.matrix(lat), as.matrix(lat_handle)))
)
#> eager_slot handle_slot same_reconstruction
#> 1 dgeMatrix BasisHandle TRUEThe eager object stores a materialized matrix; the handle object
stores a BasisHandle. Both reconstruct to the same values —
the handle just defers the cost until you call basis(),
series(), or as.matrix().
Use vignette("compression-diagnostics") when you want to
compare error and storage budgets across component counts.