The AnnData Proteomics Bridge (APB) converts the reports of
quantification software such as DIA-NN, Spectronaut or MaxQuant into one
result layout. The columns keep the vendors’ names, so a posterior error
probability is PEP in MaxQuant and EG_PEP in
Spectronaut. apb-catalog records what each column means; apbCatalogR
implements its API in R, so an R application asks for a meaning and gets
the column or its data.
The package works on a bundle apb-catalog releases: the reviewed catalogue sets and the levels apb2 converts for each software version. One bundle ships with the package, together with a test bundle of synthetic results and the answers apb-catalog gives on them.
Who does what. The integrator converts the results of quantification software such as DIA-NN, Spectronaut, AlphaDIA and Sage with apb2 and its parsing rules into an APB result, and fills the catalog. Bioconductor applications such as prolfqua, exploreDE and QFeatures read the result by meaning through the apb-catalog API: apbCatalogR in R, apb-catalog in Python, apb-catalog-ts in TypeScript.
The main calls:
Catalog$new(software, version, set): the fields a
version reportsread_parsed_levels(path): an APB result, one anndataR
AnnData per levelDataCatalog$new(result, ...): the data of a result,
looked up by keycatalogues(), catalogue_table(): the sets
and their entriesrule_coverage(): how each software version covers a
setcatalog_version(), update_catalog(): the
bundle in use, and a newer oneEach catalogue set serves one kind of consumer:
catalogues()[, c("catalogue", "has_requirements", "entries")]
#> catalogue has_requirements entries
#> 1 abundance TRUE 45
#> 2 confidence FALSE 26
#> 3 proteobench_entrapment FALSE 7
#> 4 apb TRUE 268
#> 5 chromatography FALSE 58catalogue_table() lists the entries of a set, one row
per field and software version pattern:
entries <- catalogue_table("confidence")
columns <- c("software_version_pattern", "level", "name", "key")
entries[entries$source == "Spectronaut", columns]
#> software_version_pattern level name key
#> 29 ^(19|20)\\..* peptidoform_ion EG_PEP pep
#> 30 ^15\\..* peptidoform_ion EG_PEP pep
#> 31 ^21\\..* peptidoform_ion EG_PEP pep
#> 32 ^(19|20)\\..* peptidoform_ion EG_Qvalue q_value
#> 33 ^15\\..* peptidoform_ion EG_Qvalue q_value
#> 34 ^21\\..* peptidoform_ion EG_Qvalue q_value
#> 35 ^(19|20)\\..* protein PG_QValue_Run_Wise q_value
#> 36 ^21\\..* protein PG_QValue_Run_Wise q_value
#> 37 ^(19|20)\\..* protein PG_Qvalue q_value
#> 38 ^21\\..* protein PG_Qvalue q_value
#> 39 ^21\\..* protein PG_LibraryQvalue library_q_value
#> 40 ^21\\..* peptidoform_ion EG_LibraryQvalue library_q_valueCatalog answers from the catalogue alone, without data.
Its levels are the levels apb2 converts for that software
version, each with the slots layers, var and
obs, holding field names:
catalog <- Catalog$new("DIA-NN", "2.2.0", "abundance")
catalog
#> <Catalog abundance: peptidoform_ion, protein>
catalog$levels$peptidoform_ion$layers$keys()
#> [1] "intensity" "ms1_intensity"
#> [3] "normalized_intensity" "normalized_ms1_intensity"
catalog$levels$peptidoform_ion$layers[["intensity"]]
#> [1] "Precursor_Quantity"
catalog$levels$protein$layers[["maxlfq"]]
#> [1] "PG_MaxLFQ"[ on var or obs names every
column the keys read. Spectronaut 21 reports two protein q-values:
spectronaut <- Catalog$new("Spectronaut", "21.0", "confidence")
spectronaut$levels$protein$var$keys()
#> [1] "library_q_value" "q_value"
spectronaut$levels$protein$var[c("q_value", "library_q_value")]
#> [1] "PG_Qvalue" "PG_LibraryQvalue"$fields() lists every catalogued field of the version as
a table; each entry’s id names the software, version, level and slot
where the field lives:
spectronaut$fields()[, c("key", "entry_id")]
#> key entry_id
#> 1 pep Spectronaut/*/peptidoform_ion/layers/EG_PEP/-
#> 2 q_value Spectronaut/*/peptidoform_ion/layers/EG_Qvalue/-
#> 3 library_q_value Spectronaut/21/peptidoform_ion/var/-/EG_LibraryQvalue
#> 4 q_value Spectronaut/21/protein/layers/PG_QValue_Run_Wise/-
#> 5 q_value Spectronaut/21/protein/var/-/PG_Qvalue
#> 6 library_q_value Spectronaut/21/protein/var/-/PG_LibraryQvalueread_parsed_levels() reads a result apb2 wrote,
.parquet, .h5ad or .duckdb, into
ParsedLevels, one anndataR AnnData per level.
The test bundle carries synthetic Parquet results; this one has
MaxQuant’s peptidoform-ion level.
results <- tempfile()
archive <- system.file(
"extdata", "test-bundle", "results.tar.gz",
package = "apbCatalogR"
)
utils::untar(archive, exdir = results)
result <- read_parsed_levels(
file.path(results, "maxquant_wide-peptidoform_ion.parquet")
)
result
#> <ParsedLevels>
#> peptidoform_ion: 2 x 3, layers: Intensity, MS_MS_Count, Retention_Time, Retention_Length, Calibrated_Retention_Time, Calibrated_Retention_Time_Start, Calibrated_Retention_Time_Finish, Score, PEP
result$levels$peptidoform_ion
#> InMemoryAnnData object with n_obs × n_vars = 2 × 3
#> obs: 'Raw_File', 'Experiment', 'Fraction'
#> var: 'ProForma_ion', 'Sequence', 'Modified_Sequence', 'Modifications', 'Charge', 'Mass', 'Proteins', 'Leading_Proteins', 'Leading_Razor_Protein', 'Leading_Proteins_Legacy', 'Leading_Razor_Protein_Legacy', 'Razor_Protein_Group', 'Protein_IDs', 'ProForma_peptidoform', 'ProForma_peptide', 'Reverse', 'Decoy', 'Potential_Contaminant', 'apb_Decoy', 'apb_Contaminant'
#> uns: 'apb'
#> layers: 'MS_MS_Count', 'Retention_Time', 'Retention_Length', 'Calibrated_Retention_Time', 'Calibrated_Retention_Time_Start', 'Calibrated_Retention_Time_Finish', 'Score', 'PEP'DataCatalog answers on the result and hands back the
data:
confidence <- DataCatalog$new(result, "confidence")
pep <- confidence$levels$peptidoform_ion$layers[["pep"]]
dim(pep)
#> [1] 2 3$fields() lists the catalogued fields the result holds,
from the confidence set and from apb, which is
always in:
confidence$fields()[, c("set", "location", "name", "key", "present")]
#> set location name key present
#> 1 confidence layers PEP pep TRUE
#> 2 apb var ProForma_peptide peptide TRUE
#> 3 apb var ProForma_peptidoform peptidoform TRUE
#> 4 apb var ProForma_ion peptidoform_ion TRUE
#> 5 apb var apb_Decoy decoy TRUE
#> 6 apb var apb_Contaminant contaminant TRUE
#> 7 apb var Protein_IDs protein_assignment TRUE
#> 8 apb var Proteins fasta_accessions TRUEEach level of the catalog has anndataR’s slots. A name is read as a
key first, then as the level’s own layer or column, as cf_xarray reads
ds.cf[...]; apb, APB’s own fields, is always
in:
ion <- confidence$levels$peptidoform_ion
dim(ion$layers[["pep"]]) # a matrix, runs x features
#> [1] 2 3
ion$layers$keys() # a character vector
#> [1] "pep"
head(ion$var[["peptidoform_ion"]]) # a vector, one value per feature
#> [1] "F0" "F1" "F2"The slots read the anndataR AnnData of the level, so a
key gives the same data as the vendor’s name in anndataR. A vendor’s own
name still reads, and [ gives a data frame, as on
anndataR’s obs and var:
# The anndataR AnnData of the level
adata <- result$levels$peptidoform_ion
identical(ion$layers[["pep"]], adata$layers[["PEP"]])
#> [1] TRUE
identical(ion$X, adata$X)
#> [1] TRUE
ion$obs_names
#> [1] "S0" "S1"
ion$var_names
#> [1] "F0" "F1" "F2"
# MaxQuant's own layer, as adata$layers[["Score"]]
ion$layers[["Score"]]
#> F0 F1 F2
#> S0 0.7 0.8 0.9
#> S1 0.8 0.9 1.0
# A data frame, as adata$obs[c("Raw_File", "Experiment")]
ion$obs[c("Raw_File", "Experiment")]
#> Raw_File Experiment
#> 1 S0 v0
#> 2 S1 v1
# Keys, each column named as the result names it
ion$var[c("peptidoform_ion", "decoy", "contaminant")]
#> ProForma_ion apb_Decoy apb_Contaminant
#> 1 F0 TRUE TRUE
#> 2 F1 FALSE FALSE
#> 3 F2 TRUE TRUEMaxQuant reports no q-value on this level, so $get()
gives NULL, where [[ signals an error;
$name() names the field a key reads:
A lookup the catalogue cannot answer with one field signals
UnresolvedField rather than choosing silently. FragPipe
does not document whether its intensity is normalized, so the key
intensity, never normalized, is unknown there:
fragpipe <- Catalog$new("FragPipe", "22.0", "abundance")
tryCatch(
fragpipe$levels$peptidoform_ion$layers[["intensity"]],
UnresolvedField = function(condition) conditionMessage(condition)
)
#> [1] "intensity (kind=intensity, normalization=none) on peptidoform_ion is unknown: Intensity leaves normalization unknown; candidates: Intensity"The same lookup reads each software’s field; the test bundle holds results of many:
files <- c(
MaxQuant = "maxquant_wide-peptidoform_ion.parquet",
Spectronaut = "spectronaut_v21-peptidoform_ion.parquet",
`DIA-NN` = "diann_v2-peptidoform_ion.parquet"
)
pep_field <- function(file) {
vendor <- read_parsed_levels(file.path(results, file))
catalog <- DataCatalog$new(vendor, "confidence")
catalog$levels$peptidoform_ion$layers$name("pep")
}
vapply(files, pep_field, character(1))
#> MaxQuant Spectronaut DIA-NN
#> "PEP" "EG_PEP" "PEP"A protein level reads the same way; DIA-NN’s MaxLFQ intensity is the
key maxlfq:
DataCatalog$new() composes several sets on one result. A
key may leave a qualifier open, such as the unit of a retention time;
$get() and $name() narrow it, and a lookup no
field answers gives NULL:
both <- DataCatalog$new(result, "confidence", "chromatography")
both
#> <DataCatalog confidence, chromatography, apb: peptidoform_ion>
rt <- both$levels$peptidoform_ion
rt$layers$keys()
#> [1] "aligned_peak_end" "aligned_peak_start" "aligned_retention_time"
#> [4] "peak_width" "pep" "retention_time"
rt$layers$name("retention_time", unit = "minute")
#> [1] "Retention_Time"
rt$layers$get("retention_time", unit = "second")
#> NULLrule_coverage() checks every rule against one set; a set
with requirements judges each level:
coverage <- rule_coverage("abundance")
unique(coverage[, c("software", "version", "level", "verdict")])[1:5, ]
#> software version level verdict
#> 1 AlphaDIA 1.10 peptidoform_ion warn
#> 3 AlphaDIA 1.12 peptidoform_ion warn
#> 5 AlphaDIA 2.x peptidoform_ion warn
#> 7 AlphaDIA 2.x protein <NA>
#> 8 AlphaPept 0.5 peptidoform_ion passupdate_catalog() downloads the bundle of the latest
apb-catalog release into the user cache; later sessions use the newest
bundle, shipped or downloaded. It works from the next apb-catalog
release, the first to ship the bundle.
sessionInfo()
#> R version 4.6.2 (2026-10-29)
#> 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
#> [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] 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] apbCatalogR_0.99.0 BiocStyle_2.41.0
#>
#> loaded via a namespace (and not attached):
#> [1] Matrix_1.7-6 bit_4.6.0 jsonlite_2.0.0
#> [4] compiler_4.6.2 BiocManager_1.30.27 tidyselect_1.2.1
#> [7] Rcpp_1.1.2 assertthat_0.2.1 anndataR_1.3.3
#> [10] jquerylib_0.1.4 png_0.1-9 arrow_26.0.0
#> [13] yaml_2.3.12 fastmap_1.2.0 reticulate_1.47.0
#> [16] lattice_0.23-1 R6_2.6.1 knitr_1.52
#> [19] maketools_1.3.2 bslib_0.12.0 rlang_1.3.0
#> [22] cachem_1.1.0 xfun_0.62 sass_0.4.10
#> [25] sys_3.4.3 bit64_4.8.6 otel_0.2.0
#> [28] cli_3.6.6 magrittr_2.0.5 digest_0.6.39
#> [31] grid_4.6.2 lifecycle_1.0.5 vctrs_0.7.3
#> [34] evaluate_1.0.5 glue_1.8.1 buildtools_1.0.0
#> [37] rmarkdown_2.32 purrr_1.2.2 tools_4.6.2
#> [40] htmltools_0.5.9