Materialises an in-memory mif into a self-describing directory and returns a
disk-backed mif that reads from it. Metric functions then load only the
columns they need, for the one sample a worker is handling, instead of the
parent process holding every sample resident for the whole run.
Arguments
- mif
object of class
mifcreated withcreate_mif().- path
directory to write the store to. Required, with no default – a store can be tens of gigabytes, so it is always written where you say and never somewhere chosen for you.
~is expanded.- overwrite
whether to replace an existing store at
path.
Details
Use this when the spatial data no longer comfortably fits in memory, which in
practice means whole slide images. On a 283-slide cohort of 342 million cells the
in-memory spatial slot is 34.2 GB against 2.19 GB of parquet, and since the
parent process must hold all of it while mclapply() forks, a multi-core run
cannot start at all. Disk-backing makes peak memory scale with workers rather
than with the number of samples.
It is not a general speed-up. For ripleys_k() the per-worker pair list
overtakes the spatial frame at roughly max(r_range) = 30, so at the default
r_range the frame is a minor part of a worker's footprint; what disk-backing
removes is the parent's copy of the whole cohort.
The store is a plain directory you can inspect, copy and archive:
cohort.mif/
manifest.json format, ids, and per-sample rows/columns/size
clinical.rds sample.rds
spatial/ <sample>.parquet one file per sample, never rewritten
overlay/ <sample>.parquet columns added later by split_tissue()
derived/ <slot>.rds
Paths inside manifest.json are relative, so the whole directory can be moved
or copied and still open.
What is and is not preserved
Column names, column order, types and factor levels – including unused levels
and NAs – round-trip exactly, so collect_mif(mif_to_disk(x, p)) returns the
spatial data x had.
The spatial slot is iterable, so sapply(mif$spatial, nrow) and
lapply(mif$spatial, ...) work the same way they do on an in-memory mif. But
iterating reads every sample, because lapply() materialises its input before
applying anything, so peak memory becomes the whole cohort — the cost
disk-backing exists to avoid. On a cohort that does not fit, iterate over names
instead and only one sample is resident at a time:
# reads everything at once
sapply(mif$spatial, nrow)
# reads one sample at a time
vapply(names(mif$spatial), function(s) nrow(mif$spatial[[s]]), numeric(1))Writing to the slot is refused outright — mif$spatial[[i]] <- x and
length(mif$spatial) <- n error rather than destroying the index. Use
collect_mif() to bring the data into memory if you need to modify it.
The store is shared state
A metric writes its results into the store as well as into the mif it returns,
so they survive a reopen. One consequence is worth knowing, because it is the one
place a disk-backed mif does not behave like an ordinary R object:
a <- ripleys_k(xd, ...) # result goes to `a`
identical(open_mif(store)$derived, xd$derived) # FALSE
identical(open_mif(store)$derived, a$derived) # TRUEThe in-memory xd is untouched, exactly as copy-on-modify implies — but xd and
a point at the same directory, and the metric wrote there. The store reflects
whichever mif most recently ran a metric against it.
Nothing is silently dropped: each run carries forward the derived slots it
inherited, so only the slot being written is replaced. Assigning the result back
to the same name (xd <- ripleys_k(xd, ...)) keeps object and store in step. To
hold two parameterisations at once, give them separate stores rather than separate
variables.
Row names are not preserved; they come back as 1:nrow. A columnar file has
nowhere to put them, and storing them would mean an extra column per sample for
something no function in this package reads – every metric indexes cells by
position. Non-default row names only arise from having subset a data frame
(spat[keep, ]), and dplyr resets them anyway, so this is visible only if you
compare with identical() after such a subset. Use
all.equal(check.attributes = FALSE), or reset them yourself, if you need that
comparison to hold.
See also
open_mif() to reopen one, collect_mif() to load it back into memory.
Examples
x <- create_mif(clinical_data = spatialTIME::example_clinical,
sample_data = spatialTIME::example_summary,
spatial_list = spatialTIME::example_spatial[1],
patient_id = "deidentified_id", sample_id = "deidentified_sample")
store <- file.path(tempdir(), "example.mif")
xd <- mif_to_disk(x, store, overwrite = TRUE)
xd
#> 229 patients spanning 229 samples and 1 spatial data frames were found
unlink(store, recursive = TRUE)
