Summary
escape.matrix() errors during the normalization step when handed a SpatialExperiment object. Enrichment itself completes (all 167 chunks process, expressed-gene counts are computed) and the failure occurs only once normalization begins.
Reported by Cathal King, who has generously offered to share the SPE object so we can reproduce and test against real spatial data. Acknowledgment to be added to DESCRIPTION and package docs (see Documentation section).
Reproducing report
trial <- escape.matrix(spe,
method = "AUCell",
normalize = TRUE,
gene.sets = .gs,
min.size = NULL)
Output:
escape.matrix(): processing 167 chunk(s)...
Computing expressed-gene counts per cell...
Normalizing enrichment scores...
Error in h(simpleError(msg, call)) :
error in evaluating the argument 'x' in selecting a method for function 't':
unable to find an inherited method for function 'assay' for signature 'x = "NULL", i = "missing"'
spe is a SpatialExperiment (~167k spots given the chunk count, likely multi-section).
What the error tells us
The failing expression is t(assay(<NULL>)) — i.e. something upstream returned NULL and was passed straight into assay(). Two observations narrow this down:
- Chunked enrichment succeeded, so
.cntEval() handled the SPE fine — counts extraction is not the problem.
Computing expressed-gene counts per cell... printed, so the per-cell feature counts were also retrieved successfully from the SPE.
- The failure lands between
Normalizing enrichment scores... and any output — i.e. while retrieving the enrichment matrix, not while computing anything.
Working hypothesis
When normalize = TRUE, escape.matrix() passes the freshly computed scores down to performNormalization(). If the normalization path branches on the class of input.data rather than on whether enrichment.data was supplied, then for any SummarizedExperiment-derived input it will ignore the in-memory scores and try to pull them back off the object via .pull.Enrich() / altExps(sc)[[assay]]. Since escape.matrix() never writes an assay to the object, that name lookup returns NULL (list [[ on a missing name returns NULL rather than erroring), and t(assay(NULL)) produces exactly the observed signature error.
Important corollary: if this is right, the bug is not spatial-specific — it should reproduce with a plain SingleCellExperiment too, and SpatialExperiment is simply the object class that surfaced it. Confirming or refuting that is step 1, because it changes the scope of the fix.
Secondary possibilities to rule out:
- SPE-specific behavior of
altExp() / altExps() (SPE extends SCE, so is() checks pass, but the int_colData machinery differs).
- An assay-name assumption (
"counts" vs "logcounts") — read10xVisium() objects ship with counts only. Less likely given the counts step succeeded, but cheap to check.
Tracking checklist
1. Diagnosis
Minimal SPE for local testing without the shared data:
library(SpatialExperiment); library(SingleCellExperiment)
sce <- Seurat::as.SingleCellExperiment(SeuratObject::pbmc_small)
spe <- SpatialExperiment(
assays = list(counts = counts(sce), logcounts = logcounts(sce)),
colData = colData(sce),
spatialCoords = matrix(runif(2 * ncol(sce)), ncol = 2,
dimnames = list(colnames(sce), c("x", "y")))
)
GS <- list(Bcells = c("MS4A1","CD79B","CD79A"), Tcells = c("CD3E","CD3D","CD3G"))
escape.matrix(spe, method = "AUCell", normalize = TRUE, gene.sets = GS, min.size = NULL)
2. Fix
3. Tests
4. Documentation
Acceptance criteria
escape.matrix() and runEscape() complete with normalize = TRUE on a SpatialExperiment, results are numerically identical to the equivalent counts-matrix call, spatial metadata survives runEscape(), and any bad assay name produces a readable error instead of an S4 dispatch failure.
Interim workaround for users
scores <- escape.matrix(as.matrix(counts(spe)),
method = "AUCell",
normalize = TRUE,
gene.sets = .gs,
min.size = NULL)
Bypasses the SE dispatch path entirely; scores can be attached back to spe manually.
Summary
escape.matrix()errors during the normalization step when handed aSpatialExperimentobject. Enrichment itself completes (all 167 chunks process, expressed-gene counts are computed) and the failure occurs only once normalization begins.Reported by Cathal King, who has generously offered to share the SPE object so we can reproduce and test against real spatial data. Acknowledgment to be added to
DESCRIPTIONand package docs (see Documentation section).Reproducing report
Output:
speis aSpatialExperiment(~167k spots given the chunk count, likely multi-section).What the error tells us
The failing expression is
t(assay(<NULL>))— i.e. something upstream returnedNULLand was passed straight intoassay(). Two observations narrow this down:.cntEval()handled the SPE fine — counts extraction is not the problem.Computing expressed-gene counts per cell...printed, so the per-cell feature counts were also retrieved successfully from the SPE.Normalizing enrichment scores...and any output — i.e. while retrieving the enrichment matrix, not while computing anything.Working hypothesis
When
normalize = TRUE,escape.matrix()passes the freshly computed scores down toperformNormalization(). If the normalization path branches on the class ofinput.datarather than on whetherenrichment.datawas supplied, then for anySummarizedExperiment-derived input it will ignore the in-memory scores and try to pull them back off the object via.pull.Enrich()/altExps(sc)[[assay]]. Sinceescape.matrix()never writes an assay to the object, that name lookup returnsNULL(list[[on a missing name returnsNULLrather than erroring), andt(assay(NULL))produces exactly the observed signature error.Important corollary: if this is right, the bug is not spatial-specific — it should reproduce with a plain
SingleCellExperimenttoo, andSpatialExperimentis simply the object class that surfaced it. Confirming or refuting that is step 1, because it changes the scope of the fix.Secondary possibilities to rule out:
altExp()/altExps()(SPE extends SCE, sois()checks pass, but theint_colDatamachinery differs)."counts"vs"logcounts") —read10xVisium()objects ship withcountsonly. Less likely given the counts step succeeded, but cheap to check.Tracking checklist
1. Diagnosis
SingleCellExperimentreproduces the error — determines if this is an SPE bug or a general SE-input bug.as(spe, "SingleCellExperiment")to isolate SPE-specific behavior.normalize = FALSEcompletes cleanly on the same object.escape.matrix(as.matrix(counts(spe)), normalize = TRUE, ...)) works — establishes the workaround and confirms the branch.debug(escape:::performNormalization)on Cathal's object; capturetraceback()and identify the exactNULLreturn.Minimal SPE for local testing without the shared data:
2. Fix
performNormalization(), give precedence to a non-NULLenrichment.dataregardless ofinput.dataclass — never re-pull scores that were handed in.assayNames()/altExpNames()(Seurat:Assays()) andstop()with an actionable message naming what was requested and what is available, rather than propagatingNULL.NULLcan never reachassay().runEscape()on an SPE round-trips correctly:spatialCoords(),imgData(), andsample_idpreserved, andcolnamesalignment maintained when the result is attached as analtExp.sample_ids in one SPE) — relevant to normalizationgroupsand to Cathal's object specifically.3. Tests
escape.matrix(spe, normalize = TRUE)returns a matrix of the expected dimensions.SingleCellExperiment(regression guard for the general case).performNormalization()with an explicitenrichment.dataand an SEinput.datauses the supplied data.SpatialExperimenttoSuggestsand skip these tests if unavailable.4. Documentation
DESCRIPTIONAuthors@Rasctb, with an acknowledgment for contributing the spatial test data.NEWS.mdentry for the fix, crediting the report.SpatialExperimentsupport explicitly inescape.matrix()/runEscape()/performNormalization()man pages.runEscape()attaches, and the spatial-coordinate caveat).Acceptance criteria
escape.matrix()andrunEscape()complete withnormalize = TRUEon aSpatialExperiment, results are numerically identical to the equivalent counts-matrix call, spatial metadata survivesrunEscape(), and any bad assay name produces a readable error instead of an S4 dispatch failure.Interim workaround for users
Bypasses the SE dispatch path entirely; scores can be attached back to
spemanually.