001 and
1 remain distinct while measurement columns retain numeric parsing.
QuPath retains original Image labels and refuses distinct images that would
collapse to one parsed sample identity, including across files.NaN.PermutationCorrelation() also validates its tolerance and
exact-enumeration limit, preventing factor-code analyses, invalid exact
p values from negative tolerances, and infinite permutation loops.
Pearson permutation correlations are stable for finite observations at
very large or small numeric scales.RunUMAP() uses one worker thread and base SVD by default; n_threads
and pca_method can be changed explicitly. Base SVD avoids an older-R
failure in the optional truncated solver during PCA initialisation of
disconnected groups. The visualisation vignette also uses a fixed seed.ExactPairedTests() (exact
signed-rank test over all sign flips with midranks, exact sign test,
direction interval and leave-one-out rows), PairedDifferences(),
ContrastPatientEndpoints(), BootstrapMedianInterval(),
PermutationCorrelation(), NeighbourTypeCounts() (Delaunay neighbours
within a maximum edge length, GEOS or deldir), SummarizeNearestDistances(),
SummarizeCellComposition(), ReadCellArrays() and keyed comparisons with
CompareTables(), CompareArrays() and SummarizeComparisons().ManuscriptStyle(), page composition in millimetres
(ComposeFigure(), FigurePanel(), SaveFigure()) and figures rendered
from declarative specifications (ReadFigureSpec(), RenderFigure(),
WriteFigure()) through registered plot grammars, with plot-data builders
for patient-level, matrix, embedding and image panels.PlotData*() (select, reshape,
join, bind, recode, summarise, count, rank, compute and write text), with
filter and into as common argument names, and one template syntax in
FormatTemplate().NiceBreaks() (also with
intervals that follow the axis length); LegendKeys() gains marker sizes,
spacings, column-major filling and diamond and patch keys; figure texts can
sit on their baseline; typesetting can be switched off per figure, panel,
text or legend, or for single rules; straight quotes stay straight.SaveFigure() and WriteFigure() give the same files whatever was drawn
before in the session: with 'ragg', every PNG is drawn on a new capture
device, so rich text is measured afresh for every figure.ReadCosMx() matches cells on fov and cell_ID: CosMx numbers cells
within each FOV, and cells of later FOVs used to receive the expression of
the first. ReadMatrixCoords() takes further id columns in cell_id_col
and refuses ids that are not unique.FindNeighbours(), CellDensity(), nearest_neighbours() and cell_density()
return cells in input order; with interleaved samples, values used to be
written to the wrong cells.BuildExactDelaunayEdges() and the "deldir" engine of
NeighbourTypeCounts() triangulate through the public deldir::deldir()
instead of deldir's internal routines, with the same edges. The deldir
version pin is gone; backend_contract is no longer needed and is ignored,
and mode, initial_madj and max_retries no longer change the
construction. Frames that need deldir to enlarge its workspace now succeed
in every mode; frames of 799,800 or more points are refused. Some degenerate
frames that the bounded mode used to triangulate now fail with deldir's own
error ("Bailing out of adjchk."), for example a centre with 40 points on a
circle around it, a regular 150-gon, or 50 collinear points with two points
off the line.ExactPatientTests() (and so ContrastPatientEndpoints() and
LeaveOnePatientOut()) computes the observed and the allocated statistics
with one formula on centred values and counts differences within the
rounding of that arithmetic and of the stored values as ties. p values no
longer change when values are rescaled, or shifted while the rounding of the
shifted values stays below the spacing of distinct statistics (whole numbers
keep their exact p values up to shifts of 1e13). Before, the observed
statistic used a different formula and a fixed tolerance, so for values of
about 10^3 and above with decimals the observed allocation or its mirror
could be missed and p came out too small by 1/allocations, down to 0; such
p values now equal exact enumeration. Results for other inputs are
unchanged.DelaunayNetwork() maps deldir's points back to cell rows when coordinates
repeat; edges used to point to the wrong cells after a duplicate.read_spatial() fills a blank QuPath Image value with the sample id;
the blank used to stay.interaction_matrix() counts neighbours in a table without sample_id
as one sample; it used to return zeros.CrossNNDistance() with from equal to to measures to the nearest other
cell; every cell used to find itself at distance 0.PhenotypeCells() and phenotype_cells() treat a missing intensity as not
positive; such cells used to get labels such as "CD3+/NA+".NormaliseData(),
normalise_markers()) keeps missing values instead of setting them to 0.{n|d} writes every digit (100000, not 1e+05).
Plain placeholders and the comma format write whole numbers of magnitude
2^31 or more with every digit instead of "NA" and a warning.PlotBars() and PlotStackedBars() place the reference label
and the top values on the value axis; their coordinates used to be swapped.AlterationMatrixData() puts the genes in last last also when they are
altered in at least min_count samples.PlotDataSummarise() with groups returns no rows when filter selects
nothing, instead of one row with a missing group; PlotDataCount() without
groups returns n = 0 instead of failing.PlotDataCrossShares() computes totals and shares for an empty-string
level, which used to get missing values.ExactPairedTests(), BootstrapMedianInterval(),
PermutationCorrelation(), CompareTables() and SummarizeComparisons();
without group columns they give one unavailable overall row.CellPositionsFor() with zero references, SchematicShapes() without shapes
and PlotDataQuantiles() with zero rows give typed empty tables; they used
to fail, warn or return NULL.PermutationCorrelation() refuses min_n below 2; a group with a single
pair used to stop with an unhelpful error.PairedDifferences() refuses id or name columns called x, y or
difference, which used to produce duplicated column names.PairedDifferences() gives zero rows for a zero-row table also for pairs
without y, which used to fail.FormatTemplate() writes infinite values of plain placeholders as "n.e."
instead of failing.RenderFigure() passes the colour keyword "group" (for example
label_colour = "group") on to the builder instead of rejecting it.PlotDataCount() and PlotDataRankRows() accept data.table input.PlotDataDominant() gives a group without any share a missing dominant
category instead of failing.SummarizeGroupedValues() accepts integer value and weight columns, which
used to fail when groups gave different column types.AssignFixedGrid() corrects a bin index that the rounded division put one
bin off, so coordinates on bin edges (for example x = 10.7 with size 7
and offset 3.7) are assigned instead of failing.NA instead of an error.PhenotypeCells() and phenotype_cells() phenotype a single cell on several
markers instead of failing.QCFilter() removes cells with a missing area when an area threshold is set,
as qc_filter() does; it used to fail.CellMap(), plot_cell_map() and DimPlot() colour by a numeric column with a
continuous scale instead of failing.as_Seurat() makes cell IDs that repeat across samples unique for the Seurat
cell names instead of failing.png::readPNG() when 'ragg' is not installed.ExpressionClusters(method = "gmm") no longer attaches 'mclust' to the
search path; the clusters are the same.RunUMAP(densmap = TRUE) uses the densMAP approximation of uwot::umap()
(dens_scale = 1); it used to fail because 'uwot' has no densmap()..npz archives in CompareArrays() and PlotDataFromArrays()
needs a 'cellspecR' newer than 1.0.0 (with cs_read_npz()); with
'cellspecR' 1.0.0 both functions now say so instead of failing on a missing
function.RunUMAP(), RunTSNE() and RunSONG() with seed restore the caller's
random number stream on exit instead of leaving it reseeded; the embeddings
for a given seed are unchanged.RunSONG() error install 'cellspecR' and
'songR' from the CTTIR R-universe with install.packages().Standardize the named v5 package logo across README, documentation pages and available application headers, with matching light and dark artwork.
Prepare a release with explicit patient-level inference, grouped summaries, spatial-table projection, and the existing single-cell spatial analysis APIs.
Document GitHub access for the optional cellspecR and songR packages and correct the SONG installation hint. Conditional dependency checks remain in place; no analytical algorithms or public function signatures change.
Align package and citation metadata, retain release notes in source archives, and distinguish current release information from earlier development labels.
The entries below retain the earlier development record. Their original version labels are historical project labels, not a sequence of CRAN releases. The package version for this release is 0.1.1.
Project explicit spatial-table rows and columns while retaining column metadata, repeated-row order and typed empty results without changing inputs.
Apply supplied group thresholds with strict comparisons, validated reference coverage and separate eligibility and missing-value provenance.
Complete scalar observations against an explicit registry and absent-value policy while preserving observed missingness, metadata and row provenance.
Summarize grouped subset counts with exact bounded count totals, propagated missingness and explicit zero-denominator status before fraction division.
Assemble explicitly registered paired summaries with missing-side, count and value availability retained separately from descriptive contrasts.
Build complete explicitly ordered Delaunay geometries with caller-pinned native contracts, full-graph validation before eligibility induction, and pure integer workspace preflight. Unsupported backends or geometry fail without fallback.
LeaveOnePatientOut() evaluates explicitly registered patient omissions using
the existing exhaustive exact test, retaining incomplete-full and empty-arm
outcomes, availability counts and effect-direction diagnostics.
Contrast explicit groups within finite-metric overlap strata while retaining policy, evaluable and matched denominators, per-observation weights, stable effective control counts and separately requested quality criteria.
Count supplied undirected edges between explicit disjoint node sets using complete group/frame registries, stable IDs, isolated-node denominators and separate zero-denominator statuses without claiming geometry reconstruction.
Explicit grouped rank correlations retain paired availability counts, ties and constant-value statuses without manufacturing absent groups.
Grouped summaries expose the available weight total as an explicit denominator, retaining unknown and observed-zero weight totals separately.
Add explicit grouped continuous summaries with separate value/weight availability and caller-declared missingness and minimum-count policies.
Add keyed fixed-grid annotation summaries with explicit unknown category and indicator counts, preserving sample boundaries and incomplete fractions.
Add explicit physical fixed-grid membership with sample/frame validation, deterministic keys, and fail-closed numerical containment checks.
ExactPatientTests() enumerates declared independent-patient contrasts with
complete-patient missingness and explicit allocation resolution.
AdjustPatientFamilies() retains unavailable hypotheses and reports both
finite-only and declared-family BH/BY adjustments. Existing differential
abundance methods are unchanged.
FromCellspec() preserves canonical feature dictionaries, missing features,
cell keys, physical coordinates and original-import provenance. Independent
images become separate spatial samples even when biological sample IDs match.
Delaunay networks now triangulate each sample independently and preserve full-object row indices. Missing sample IDs and groups with fewer than three cells fail explicitly instead of allowing cross-sample edges.
Cell subsetting clears cached spatial results and neighbour distance/density columns, preventing stale edges and summaries after filtering or reordering. Cluster, neighbourhood and domain labels remain frozen annotations from the original fit for plotting; recompute them to describe the new neighbourhoods. Marker-only subsetting retains spatial results.
Differential abundance rejects missing/empty conditions or sample IDs and conflicting conditions within a sample before aggregating proportions.
bslib / Bootstrap 5 and a _brand.yml, launched with run_app(). It covers
the core workflow, spatial statistics, cellular neighbourhoods, spatial
domains, dimensionality reductions, and differential abundance, with a
light/dark colour-mode toggle. The Shiny stack is optional (Suggests), so the
core package stays lightweight; run_app() reports any missing app packages.inst/app as composable Shiny modules over the package's
exported functions, with a shinytest2 smoke test (light and dark).Major feature release: higher-order spatial structure, unsupervised phenotyping, differential testing, platform readers, and ecosystem interop.
CellularNeighbourhoods()): cluster
cells by the phenotype composition of their local neighbourhood.SpatialDomains()): partition tissue into regions of
coherent, spatially smoothed expression.ExpressionClusters() gains a Gaussian
mixture-model option (method = "gmm", via mclust), and AnnotateClusters()
names clusters by their most enriched markers.DifferentialAbundance()): per-sample phenotype
proportions compared across conditions (Wilcoxon / t / Kruskal-Wallis), with
BH-adjusted p-values.ReadMatrixCoords() for the general expression-plus-
coordinates layout, with ReadXenium(), ReadCosMx(), and ReadMERSCOPE()
presetting each vendor's cell-level column names.as_Seurat(), as_SpatialExperiment(), and
as_SpatialCellData() convert to and from Seurat / SpatialExperiment.Deeper, more rigorous statistics.
RipleysK() gains a "translation" edge correction (each pair weighted by the
inverse window/translate overlap) -- the rigorous estimator for rectangular
windows, alongside the existing "none" and reduced-sample "border".MoransI() gains weight schemes (weights = "binary", "row"-standardised,
or "idw" inverse-distance) and a permutation p-value (n_perm), reported via
a method field. Non-binary weights use the permutation test automatically.InteractionMatrix() gains a permutation null (method = "permutation",
n_perm) that shuffles phenotype labels within each sample and adds
z_score and p_value columns.VarianceExplained() returns the
percent variance per component and ScreePlot() visualises it.API coherence.
phenoscapR_ripley, _pcf, _moran, _quadrat, _enrichment,
_interaction, _crossnn) that still behave like their underlying
data.frame / list / vector, with consistent print summaries and ggplot2
autoplot() methods for one-line visualisation.RipleysK, MoransI, QuadratAnalysis,
PairCorrelation, NeighbourhoodEnrichment, CrossNNDistance) gain a
by_sample argument: with by_sample = TRUE a multi-sample object is
analysed per section and returned as a named list (phenoscapR_by_sample)..lintr configuration (the package is lint-clean) and a
lint GitHub Actions workflow running lintr and goodpractice.Robustness and scalability hardening.
deldir is absent) no longer builds an
O(n^2) distance matrix; it now uses the kd-tree k-nearest-neighbour engine.CreateSpatialObject() robustness: unnamed marker matrices get
stable default names and all-NA marker columns are dropped with a warning;
ReadSpatial() now shares this handling. Duplicate marker names remain a hard
error (renaming them would mask a labelling mistake).First stable release. This version consolidates the package onto a single compute engine, hardens the spatial statistics, and ships a reproducible example dataset and comprehensive vignettes.
phenoscapR_example (two samples, eight
markers, planted spatial niches) used across all examples, tests, and
vignettes — data(phenoscapR_example).RunPCA(), RunUMAP(), RunTSNE(), RunSONG()
with DimPlot() / EmbeddingPlot() and an Embeddings() / Reductions()
accessor pair.interaction_matrix() and
friends never count neighbours across samples, and the single-window
statistics (RipleysK(), MoransI(), QuadratAnalysis(),
PairCorrelation(), NeighbourhoodEnrichment(), CrossNNDistance()) refuse
multi-sample objects with an informative error.DelaunayNetwork() builds a real Delaunay triangulation via deldir
(Suggests) instead of an all-pairs placeholder, with a max_edge cap.RANN,
Suggests) with an exact, memory-bounded base-R fallback, replacing the
per-function O(n^2) distance matrices. Interaction and enrichment counting are
vectorised and the enrichment permutation graph is computed once. Large
sections (10^4–10^5 cells) that previously needed multi-gigabyte distance
matrices now run in seconds.SpatialCellData APIs onto one internal
compute core so both faces stay in sync.inst/CITATION, expanded the test suite, and raised coverage to ~83%.Fixed selected key names such as collapse, sep and ... in grouped
summaries and rank correlations, retaining duplicate-ID validation. Shared
key validation now rejects matrix-valued keys across its public consumers.
Added ContrastGroupedRanks() for descriptive grouped Cliff deltas with
explicit arm and missingness policies, complete registries and exact available
pair counts without allocating cross-pair matrices.
Fixed sorting of native-encoded non-ASCII keys in disjoint edge counts, grouped overlap contrasts and exact patient tests while preserving returned keys and numerical results.
Added CountRadiusSets() for ID-keyed full-frame radius counts of explicit
reference sets, with complete query outputs, a required numerical search
envelope, and installed backend fingerprints.
Initial release.
Read single-cell spatial biology data with read_spatial().
Quality control with qc_filter().
Marker normalisation with normalise_markers().
Cell phenotyping with phenotype_cells() and summarise_phenotypes().
Spatial analysis: nearest_neighbours(), cell_density(),
interaction_matrix(), spatial_clusters().
Visualisation: plot_cell_map(), plot_density(), plot_heatmap(),
plot_interactions().