SpaMTP, SpaMTPdb and SpaMTPData: A Native Companion Workflow

Clear responsibilities

Package Responsibility Primary representations
SpaMTPData Discoverable experiment resources and a small synthetic example Cardinal, SpatialExperiment, auxiliary files; labelled historical archives
SpaMTPdb Versioned annotation and pathway resources Independent tables/lists with checksums and provenance
SpaMTP Analysis, annotation, plotting and explicit conversion Existing Bioconductor S4 containers and public accessors

The data packages do not import SpaMTP or Seurat. SpaMTP imports SpaMTPdb and suggests SpaMTPData for demonstrations; there is no circular runtime dependency. Use SpaMTP >= 0.99.4, SpaMTPdb >= 0.99.3 and SpaMTPData >= 0.99.4 together. The package versions are not the same thing as RaMP or experiment-data versions.

The first experiment and small chemical table are explicitly synthetic teaching inputs. Later sections load the complete pinned RaMP resources and real brain datasets, downloading them when no verified local copy or cache is available. All R code blocks are evaluated when building this article.

library(SpaMTP)
stopifnot(requireNamespace("SpaMTPData", quietly = TRUE))

Data to a native analysis container

spe <- SpaMTPData::spaMTPExampleData()
stopifnot(methods::is(spe, "SpatialExperiment"))
SummarizedExperiment::assayNames(spe)
## [1] "counts"
SummarizedExperiment::rowData(spe)$mz
## [1] 149.0444 171.0264 181.0707 203.0526 166.0863 500.0500
SingleCellExperiment::altExpNames(spe)
## [1] "transcriptome"

Counts/intensities, feature metadata, pixel metadata and coordinates have separate public accessors. The paired transcriptome already has matching pixel names and is stored in altExp(). Independent measurements must first be spatially aligned and mapped; they cannot be paired merely by renaming columns.

spe <- normalizeSMData(spe, "LogNormalize", verbose = FALSE)
spe <- runMetabolicPCA(spe, npcs = 2, slot = "logcounts")
## Warning in (function (A, nv = 5, nu = nv, maxit = 1000, work = nv + 16, : did not converge--results
## might be invalid!; try increasing maxit or work
plotSpatialFeature(spe, features = "peak1", assayName = "logcounts",
  labels = "Synthetic peak 1", pointSize = 4)

SingleCellExperiment::reducedDimNames(spe)
## [1] "pca"

Database discovery without network access

The registry is lightweight package metadata. Both access paths below describe the same resource release and use the coordinated camelCase API.

registry <- SpaMTPdb::spaMTPdbResources(version = "3.0.7")
stopifnot(identical(registry, spaMTPDatabaseInfo(version = "3.0.7")))
registry[registry$resource %in% c("chem_props", "smiles_features", "pathway"),
  c("resource", "version", "r_data_class", "bytes", "md5")]
##           resource version r_data_class    bytes                              md5
## 1       chem_props   3.0.7   data.frame 12925616 42865f00ea7445f72bcd2f66b2b315a9
## 5          pathway   3.0.7   data.frame  1933008 054f527072d3d29d4ff56c5b5e27ede5
## 16 smiles_features   3.0.7   data.frame  4252016 f492e18843643ef17b037b91f7988ecb

For a production analysis, loadSpaMTPDatabase() uses the same resource reader and retains database provenance. A staged official file must match its size and checksum; offline = TRUE can also reuse a verified download cache.

database <- loadSpaMTPDatabase(
  c("chem_props", "pathway"), version = "3.0.7"
)
stopifnot(nrow(database$chem_props) == 289754L)

Annotate without inventing a downloaded database

This small custom table contains two neutral masses and explicit illustrative identifiers. It is supplied as a custom database rather than being placed under an official resource filename with an incorrect checksum.

custom <- data.frame(
  id = c("demo-2HG", "demo-glucose"),
  name = c("2-hydroxyglutarate", "glucose"),
  formula = c("C5H8O5", "C6H12O6"),
  exactmass = c(148.037173366, 180.063388104)
)
index <- buildMZAnnotationIndex(custom, adducts = "M+H", polarity = "positive")
annotated <- annotateSM(spe, index = index, adducts = "M+H",
  ppm_error = 5, return.only.annotated = FALSE, verbose = FALSE)
stopifnot(identical(dim(annotated), dim(spe)),
  identical(colnames(SingleCellExperiment::altExp(annotated, "transcriptome")),
    colnames(spe)))
SummarizedExperiment::rowData(annotated)[,
  c("mz", "all_IsomerNames", "all_Scores")]
## DataFrame with 6 rows and 3 columns
##              mz    all_IsomerNames    all_Scores
##       <numeric>        <character>   <character>
## peak1   149.044 2-hydroxyglutarate             1
## peak2   171.026      No Annotation No Annotation
## peak3   181.071            glucose             1
## peak4   203.053      No Annotation No Annotation
## peak5   166.086      No Annotation No Annotation
## peak6   500.050      No Annotation No Annotation
annotationInfo(annotated)
## $schema_version
## [1] 2
## 
## $engine
## [1] "indexed-chemical-v2"
## 
## $ramp_version
## [1] "3.0.7"
## 
## $generated_at
## [1] "2026-10-14 02:15:40 UTC"
## 
## $candidates
## [1] 2
## 
## $assay
## [1] "main"
## 
## $raw_mz_column
## [1] "raw_mz"
## 
## $polarity
## [1] "positive"
## 
## $maldi_matrix
## [1] "unspecified"
## 
## $ppm
## [1] 5
## 
## $min_score
## [1] 0
## 
## $adducts
## [1] "M+H"
## 
## $database
## [1] "pre-built annotation index"

These accurate-mass candidates are hypotheses, not confirmed identifications or calibrated probabilities. The full annotation tutorial explains index reuse, contextual scoring and interpretation.

Published native datasets and historical archives

The published experiment registry records what the source files actually contain. The default resource release, 1.1.0, supplies seven native SpatialExperiment datasets with new URLs and hashes in Zenodo record 22733262. Historical Seurat files remain explicitly available in release 1.0.0.

native_metadata <- SpaMTPData::spaMTPData("mouse_brain_fmp10", metadata = TRUE)
stopifnot(native_metadata$version == "1.1.0",
  native_metadata$r_data_class == "SpatialExperiment",
  !native_metadata$requires_conversion)
archive <- SpaMTPData::spaMTPData("mouse_brain_fmp10",
  version = "1.0.0", metadata = TRUE)
archive[, c("resource", "version", "r_data_class", "requires_conversion")]
##            resource version r_data_class requires_conversion
## 2 mouse_brain_fmp10   1.0.0       Seurat                TRUE
stopifnot(identical(intersect(c("Seurat", "SeuratObject"), loadedNamespaces()),
  seuratBefore))

The one-time preparation recipe shipped by SpaMTPData creates a separate native RDS for each of the seven historical archives. The published files can now be retrieved directly; no Seurat conversion is needed when using them:

native <- SpaMTPData::spaMTPData("mouse_brain_dhb_striatum", version = "1.1.0")
native <- normalizeSMData(native, verbose = FALSE)
SingleCellExperiment::altExpNames(native) # SPT
## [1] "SPT"
stopifnot(identical(dim(native), c(2075L, 876L)),
  identical(intersect(c("Seurat", "SeuratObject"), loadedNamespaces()), seuratBefore))

The native files have their own checksum/provenance manifest and do not replace the original archive URLs or checksums. They preserve counts and any complete data layer; the latter is not automatically relabelled as logcounts. Optical rasters, polygons and Seurat graphs/weights/reductions are omitted. Source annotations, including historical db_3, retain their original interpretation and are not upgraded to new scored annotations.

To work with a historical archive instead, conversion remains explicit. Only this boundary example loads the suggested SeuratObject package; the preceding native analysis does not require it.

stopifnot(requireNamespace("SeuratObject", quietly = TRUE))
legacy <- SpaMTPData::spaMTPData("mouse_brain_fmp10",
  version = "1.0.0")
converted_archive <- seuratToSpatialExperiment(legacy, assay = "Spatial", image = "fov")
stopifnot(methods::is(converted_archive, "SpatialExperiment"),
  identical(dim(converted_archive), c(1538L, 3005L)))

Select coordinates, layers and images deliberately. Optical rasters, graphs and reductions are not transferred. See modern data access for the conversion contract and the native workflow for further analysis. Older PascalCase resource calls must be updated along with the coordinated companion package versions.

sessionInfo()
## R version 4.6.1 (2026-06-24)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 26.04.1 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so;  LAPACK version 3.12.0
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8       
##  [4] LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
##  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
## [10] LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
## 
## time zone: Etc/UTC
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] rmarkdown_2.32   SpaMTP_0.99.11   BiocStyle_2.41.0
## 
## loaded via a namespace (and not attached):
##   [1] splines_4.6.1               later_1.4.8                 filelock_1.0.3             
##   [4] tibble_3.3.1                lifecycle_1.0.5             httr2_1.3.0                
##   [7] sf_1.1-3                    edgeR_4.99.7                globals_0.20.0             
##  [10] lattice_0.23-1              crosstalk_1.2.2             magrittr_2.0.5             
##  [13] SpaMTPdb_0.99.4             limma_3.99.2                plotly_4.12.1              
##  [16] sass_0.4.10                 jquerylib_0.1.4             yaml_2.3.12                
##  [19] metapod_1.21.0              httpuv_1.6.17               otel_0.2.0                 
##  [22] spam_2.11-4                 sp_2.2-4                    cowplot_1.2.0              
##  [25] DBI_1.3.0                   buildtools_1.0.0            RColorBrewer_1.1-3         
##  [28] abind_1.4-8                 GenomicRanges_1.65.4        purrr_1.2.2                
##  [31] BiocGenerics_0.59.12        rappdirs_0.3.4              SpaMTPData_0.99.5          
##  [34] IRanges_2.47.5              S4Vectors_0.51.10           ggrepel_0.9.8              
##  [37] irlba_2.4.1                 listenv_1.1.0               maketools_1.3.2            
##  [40] pheatmap_1.0.13             units_1.0-1                 RSpectra_0.16-2            
##  [43] matter_2.15.0               dqrng_0.4.1                 parallelly_1.48.0          
##  [46] codetools_0.2-20            DelayedArray_0.39.8         scuttle_1.23.2             
##  [49] tidyselect_1.2.1            farver_2.1.2                ScaledMatrix_1.21.0        
##  [52] viridis_0.6.5               matrixStats_1.5.0           stats4_4.6.1               
##  [55] BiocFileCache_3.3.0         Seqinfo_1.3.2               jsonlite_2.0.0             
##  [58] CardinalIO_1.11.0           BiocNeighbors_2.7.3         e1071_1.7-17               
##  [61] progressr_1.0.0             scater_1.41.2               tools_4.6.1                
##  [64] Rcpp_1.1.2                  glue_1.8.1                  gridExtra_2.3.1            
##  [67] SparseArray_1.13.4          BiocBaseUtils_1.15.1        xfun_0.61                  
##  [70] MatrixGenerics_1.25.0       dplyr_1.2.1                 withr_3.0.3                
##  [73] BiocManager_1.30.27         fastmap_1.2.0               bluster_1.23.1             
##  [76] digest_0.6.39               rsvd_1.0.5                  R6_2.6.1                   
##  [79] mime_0.13                   RSQLite_3.53.3              utf8_1.2.6                 
##  [82] tidyr_1.3.2                 generics_0.1.4              data.table_1.18.6.1        
##  [85] FNN_1.1.4.1                 class_7.3-24                httr_1.4.9                 
##  [88] htmlwidgets_1.6.4           S4Arrays_1.13.2             ontologyIndex_2.12         
##  [91] pkgconfig_2.0.3             gtable_0.3.6                blob_1.3.0                 
##  [94] S7_0.2.2                    SingleCellExperiment_1.35.2 XVector_0.53.0             
##  [97] sys_3.4.3                   htmltools_0.5.9             dotCall64_1.2              
## [100] fgsea_1.39.2                ProtGenerics_1.45.0         SeuratObject_5.4.0         
## [103] scales_1.4.0                Biobase_2.73.2              png_0.1-9                  
## [106] SpatialExperiment_1.23.0    scran_1.41.1                knitr_1.52                 
## [109] rjson_0.2.23                nlme_3.1-171                curl_8.0.0                 
## [112] proxy_0.4-29                cachem_1.1.0                stringr_1.6.0              
## [115] Cardinal_3.15.0             BiocVersion_3.24.0          KernSmooth_2.23-27         
## [118] parallel_4.6.1              vipor_0.4.7                 AnnotationDbi_1.75.2       
## [121] pillar_1.11.1               grid_4.6.1                  vctrs_0.7.3                
## [124] promises_1.5.0              BiocSingular_1.29.2         dbplyr_2.6.0               
## [127] beachmat_2.29.3             xtable_1.8-8                cluster_2.1.8.3            
## [130] beeswarm_0.4.0              evaluate_1.0.5              magick_2.9.1               
## [133] cli_3.6.6                   locfit_1.5-9.12             compiler_4.6.1             
## [136] rlang_1.3.0                 crayon_1.5.3                future.apply_1.20.2        
## [139] labeling_0.4.3              mclust_6.1.3                classInt_0.4-11            
## [142] naturalsort_0.1.3           ggbeeswarm_0.7.3            stringi_1.8.9              
## [145] viridisLite_0.4.3           BiocParallel_1.47.0         Biostrings_2.81.9          
## [148] Matrix_1.7-6                ExperimentHub_3.3.2         bit64_4.8.6                
## [151] future_1.76.0               ggplot2_4.0.3               KEGGREST_1.53.6            
## [154] statmod_1.5.2               shiny_1.14.0                SummarizedExperiment_1.43.0
## [157] AnnotationHub_4.3.2         igraph_2.3.4                memoise_2.0.1              
## [160] bslib_0.12.0                fastmatch_1.1-8             bit_4.6.0