| Title: | Background Normalisation for Multiplex Spatial Proteomics |
|---|---|
| Description: | Implements bgnorm, a generative statistical framework for background correction, normalisation, and quality control in multiplex spatial proteomics. The method models fluorescence pixel intensities as a three-component mixture of log-normal convolutions representing background, non-specific binding, and biological signal. A deconvolution step separates the signal component from background sources, and a model-based quantile normalisation (bgnormQ) unifies measurements across markers, samples, and three-dimensional tissue slices. A Jensen-Shannon Divergence metric provides automated quality control. A Java-free reader for Akoya PhenoCycler-Fusion QPTIFF, OME-TIFF, and OME-Zarr (OME-NGFF) images is included. |
| Authors: | Dharmesh D. Bhuva [aut, cre] (ORCID: <https://orcid.org/0000-0002-6398-9157>), Rafael Tubelleza [aut] (ORCID: <https://orcid.org/0009-0007-1414-547X>), Malvika Kharbanda [aut] (ORCID: <https://orcid.org/0000-0001-9726-3023>), Agus Salim [aut] (ORCID: <https://orcid.org/0000-0003-3999-7701>) |
| Maintainer: | Dharmesh D. Bhuva <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.99.5 |
| Built: | 2026-11-10 01:56:40 UTC |
| Source: | https://github.com/BiocStaging/bgnormR |
Implements bgnorm, a generative statistical framework for background correction, normalisation, and quality control in multiplex spatial proteomics (Kharbanda and Tubelleza et al. 2026).
The framework models log-transformed fluorescence intensities as a three-component Gaussian mixture representing:
Background (empty space / instrument noise)
Non-specific binding and autofluorescence
True biological signal
Core functions:
bgnorm_pixels - pixel-level background correction
bgnorm_cells - cell-level background correction
bgnorm_sce - normalise a SummarizedExperiment / SpatialExperiment
jsd_qc - Jensen-Shannon Divergence quality metric
read_qptiff - Akoya PhenoCycler-Fusion image reader
plot_qptiff, plot_pixel_classes,
plot_distributions, plot_jsd_heatmap - plotting utilities
Maintainer: Dharmesh D. Bhuva [email protected] (ORCID)
Authors:
Dharmesh D. Bhuva [email protected] (ORCID)
Rafael Tubelleza [email protected] (ORCID)
Malvika Kharbanda [email protected] (ORCID)
Agus Salim [email protected] (ORCID)
Useful links:
Standard 3-D array subscripting with all missing-subscript combinations
supported. The result is always a QPTIFFImage, including
when a single channel is selected. To extract a plain 2-D matrix for one
channel, use unclass(img[, , "DAPI"])[, , 1L].
img[, , "DAPI"] - single channel (returns a 1-channel QPTIFFImage)
img[, , c("DAPI","CD3")] - multi-channel sub-image
img[1:512, 1:512, ] - spatial crop
## S3 method for class 'QPTIFFImage' x[i, j, k, drop = FALSE]## S3 method for class 'QPTIFFImage' x[i, j, k, drop = FALSE]
x |
A |
i |
Row indices (spatial), or missing. |
j |
Column indices (spatial), or missing. |
k |
Channel indices (numeric or character), or missing. |
drop |
Ignored; the result is always a 3-D |
A QPTIFFImage.
For eager QPTIFFImages, equivalent to unclass(x). For lazy
(on-disk) QPTIFFImages, reads all pixel data from disk.
## S3 method for class 'QPTIFFImage' as.array(x, ...)## S3 method for class 'QPTIFFImage' as.array(x, ...)
x |
A |
... |
Unused. |
A plain numeric array of dimension c(H, W, C).
Convert a QPTIFFImage to a named list of 2-D channel matrices
## S3 method for class 'QPTIFFImage' as.list(x, ...)## S3 method for class 'QPTIFFImage' as.list(x, ...)
x |
A |
... |
Unused. |
A named list of 2-D matrices, one per channel.
Drops the singleton channel dimension and returns a plain H x W
matrix. The channel name is stored in attr(result, "channel").
If the object has more than one channel, use img[, , "DAPI"] to
select one first.
## S3 method for class 'QPTIFFImage' as.matrix(x, ...)## S3 method for class 'QPTIFFImage' as.matrix(x, ...)
x |
A |
... |
Unused. |
A numeric matrix of dimension c(H, W).
arr <- array(1:12, dim = c(3, 4, 1), dimnames = list(NULL, NULL, "DAPI")) img <- as.QPTIFFImage(arr) m <- as.matrix(img) dim(m) # 3 x 4 attr(m, "channel") # "DAPI"arr <- array(1:12, dim = c(3, 4, 1), dimnames = list(NULL, NULL, "DAPI")) img <- as.QPTIFFImage(arr) m <- as.matrix(img) dim(m) # 3 x 4 attr(m, "channel") # "DAPI"
Attaches the QPTIFFImage class to any 3-D numeric array
[H x W x C]. Channel names are taken from dimnames(x)[[3]].
This is the intended way to promote a plain array produced outside
read_qptiff (e.g., the adjusted output of bgnorm_markers)
into a first-class QPTIFFImage.
as.QPTIFFImage(x, ...) ## S3 method for class 'QPTIFFImage' as.QPTIFFImage(x, ...) ## S3 method for class 'array' as.QPTIFFImage(x, metadata = list(), ...) ## S3 method for class 'DelayedArray' as.QPTIFFImage(x, metadata = list(), ...)as.QPTIFFImage(x, ...) ## S3 method for class 'QPTIFFImage' as.QPTIFFImage(x, ...) ## S3 method for class 'array' as.QPTIFFImage(x, metadata = list(), ...) ## S3 method for class 'DelayedArray' as.QPTIFFImage(x, metadata = list(), ...)
x |
A 3-D numeric array |
... |
Unused; present for S3 method consistency. |
metadata |
Optional named list of metadata to embed. Ignored when
|
A QPTIFFImage.
arr <- array(runif(20 * 20 * 3), dim = c(20, 20, 3), dimnames = list(NULL, NULL, c("DAPI", "CD3", "CD8"))) img <- as.QPTIFFImage(arr) class(img)arr <- array(runif(20 * 20 * 3), dim = c(20, 20, 3), dimnames = list(NULL, NULL, c("DAPI", "CD3", "CD8"))) img <- as.QPTIFFImage(arr) class(img)
Applies a two-component Gaussian Mixture Model to cell-level aggregated intensities for a single marker. The two components represent non-specific binding / autofluorescence and biological signal.
bgnorm_cells( x, cofactor = 150, quantile_norm = FALSE, quantile = 0.75, sample_prop = 1, ... )bgnorm_cells( x, cofactor = 150, quantile_norm = FALSE, quantile = 0.75, sample_prop = 1, ... )
x |
Numeric vector of raw cell-level intensities for one marker. Must be a plain vector; matrices and arrays are not accepted. |
cofactor |
Positive numeric cofactor for log2 transform (default 150). |
quantile_norm |
Logical; apply model-based quantile normalisation? |
quantile |
Quantile of the signal component for normalisation (default 0.75). |
sample_prop |
Numeric in (0, 1]; proportion of non-zero observations
used to fit the GMM. Default |
... |
Additional arguments forwarded to the internal GMM fitter. |
This is a cell-level approximation of the pixel-level method intended for cases where only cell-summarised intensities (e.g., mean or median intensity per cell) are available.
A BgnormResult object.
set.seed(3) # Simulate cell intensities: nonspecific + signal x <- c(exp(rnorm(300, log(200), 0.5)), exp(rnorm(100, log(1500), 0.8))) res <- bgnorm_cells(x) print(res)set.seed(3) # Simulate cell intensities: nonspecific + signal x <- c(exp(rnorm(300, log(200), 0.5)), exp(rnorm(100, log(1500), 0.8))) res <- bgnorm_cells(x) print(res)
Fits a three-component Gaussian Mixture Model (GMM) to the log2-transformed
pixel intensities for each channel in a QPTIFFImage and
deconvolves the biological signal component from background sources. All
channels are processed independently; use channels to restrict
processing to a subset. Optionally runs channels in parallel via
BiocParallel.
bgnorm_pixels( img, channels = NULL, cofactor = 150, quantile_norm = FALSE, quantile = 0.75, sample_prop = 0.1, BPPARAM = BiocParallel::SerialParam(), ... )bgnorm_pixels( img, channels = NULL, cofactor = 150, quantile_norm = FALSE, quantile = 0.75, sample_prop = 0.1, BPPARAM = BiocParallel::SerialParam(), ... )
img |
A |
channels |
Character or integer vector of channels to process, or
|
cofactor |
Positive numeric cofactor for the log2 transform. Default 150 is appropriate for 16-bit Akoya PhenoCycler-Fusion images. |
quantile_norm |
Logical; apply model-based quantile normalisation
(bgnormQ)? Default |
quantile |
Quantile of the signal component used for normalisation
when |
sample_prop |
Numeric in (0, 1]; proportion of non-zero pixels used to
fit the GMM. Values < 1 draw a random subset, accelerating fitting on
large images. Default |
BPPARAM |
A |
... |
Additional arguments forwarded to the internal GMM fitter. |
The preprocessing, model, and deconvolution steps follow Kharbanda and Tubelleza et al. 2026:
Log2-transform: .
Fit a 3-component GMM via EM (mclust), filtering zeros and optionally subsampling for speed.
Resolve component identities (background, non-specific, signal).
Apply the deconvolution formula to recover .
Optionally normalise by the 75th-percentile of the signal component (bgnormQ).
A QPTIFFImage [H x W x C] of background-adjusted
log-intensities. The per-channel BgnormResult objects
(GMM parameters, JSD) are stored as an attribute; retrieve
them with bgnorm_results(result).
bgnorm_cells, bgnorm_results,
jsd_qc, as.QPTIFFImage
set.seed(42) px <- c(exp(rnorm(500, log(50), 0.4)), exp(rnorm(400, log(300), 0.6)), exp(rnorm(100, log(2000), 1.0))) arr <- array(c(px, px), dim = c(length(px), 1L, 2L), dimnames = list(NULL, NULL, c("DAPI", "CD3"))) img <- as.QPTIFFImage(arr) res <- bgnorm_pixels(img) names(res) # channel names of the adjusted image bgnorm_results(res)[["DAPI"]] # BgnormResult for the DAPI channel # Process only selected channels res2 <- bgnorm_pixels(img, channels = "DAPI") # Real tissue image shipped with the package path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) res <- bgnorm_pixels(img, sample_prop = 0.1) bgnorm_results(res)[["PanCK"]] # per-channel model summaryset.seed(42) px <- c(exp(rnorm(500, log(50), 0.4)), exp(rnorm(400, log(300), 0.6)), exp(rnorm(100, log(2000), 1.0))) arr <- array(c(px, px), dim = c(length(px), 1L, 2L), dimnames = list(NULL, NULL, c("DAPI", "CD3"))) img <- as.QPTIFFImage(arr) res <- bgnorm_pixels(img) names(res) # channel names of the adjusted image bgnorm_results(res)[["DAPI"]] # BgnormResult for the DAPI channel # Process only selected channels res2 <- bgnorm_pixels(img, channels = "DAPI") # Real tissue image shipped with the package path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) res <- bgnorm_pixels(img, sample_prop = 0.1) bgnorm_results(res)[["PanCK"]] # per-channel model summary
Returns the named list of BgnormResult objects attached to a
QPTIFFImage by bgnorm_pixels, or NULL if
the image has not been background-normalised.
bgnorm_results(x, ...)bgnorm_results(x, ...)
x |
A |
... |
Unused. |
A named list of BgnormResult objects (one per processed
channel), or NULL.
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) adj <- bgnorm_pixels(img, markers = names(img)[1]) bgnorm_results(adj)path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) adj <- bgnorm_pixels(img, markers = names(img)[1]) bgnorm_results(adj)
Runs bgnorm_cells independently for each marker (column) and
returns an updated object with adjusted intensities in a new assay.
bgnorm_sce( x, assay.type = "counts", name = "bgnorm", cofactor = 150, quantile_norm = FALSE, quantile = 0.75, BPPARAM = BiocParallel::SerialParam(), ... )bgnorm_sce( x, assay.type = "counts", name = "bgnorm", cofactor = 150, quantile_norm = FALSE, quantile = 0.75, BPPARAM = BiocParallel::SerialParam(), ... )
x |
A |
assay.type |
Character; name of the assay to normalise. Default
|
name |
Character; name for the output assay (SCE) or returned
matrix attribute. Default |
cofactor |
Cofactor for log2 transform (default 150). |
quantile_norm |
Logical; apply bgnormQ? |
quantile |
Quantile for normalisation. |
BPPARAM |
A |
... |
Additional arguments forwarded to |
The input object with a new assay (name) holding the
adjusted intensities. The per-marker BgnormResult list is stored
in metadata(x)$bgnorm_results.
library(SummarizedExperiment) set.seed(5) counts <- matrix( exp(rnorm(2000, log(300), 0.9)), nrow = 10, ncol = 200, dimnames = list(paste0("marker", seq_len(10)), paste0("cell", seq_len(200))) ) se <- SummarizedExperiment(assays = list(counts = counts)) se <- bgnorm_sce(se, assay.type = "counts", name = "bgnorm") assayNames(se)library(SummarizedExperiment) set.seed(5) counts <- matrix( exp(rnorm(2000, log(300), 0.9)), nrow = 10, ncol = 200, dimnames = list(paste0("marker", seq_len(10)), paste0("cell", seq_len(200))) ) se <- SummarizedExperiment(assays = list(counts = counts)) se <- bgnorm_sce(se, assay.type = "counts", name = "bgnorm") assayNames(se)
A lightweight list-based S3 class returned by bgnorm_pixels
(per channel, via bgnorm_results) and bgnorm_cells.
Not applicable; documents the BgnormResult class structure.
parametersList with elements means, sds, props.
nInteger; total number of input observations (pixels or cells, including zeros) for this channel.
thresholdFor the 3-component (pixel-level) model: a single
numeric scalar giving the maximum adjusted intensity of Non-specific pixels.
Combined with the implicit class-1 boundary at 0, this encodes the full
classification: adj == 0 -> Background; 0 < adj <= threshold
-> Non-specific; adj > threshold -> Signal.
NULL for 2-component models (cell-level or no-signal fallback),
where the only boundary is 0 (adjusted > 0 -> Signal).
jsdJensen-Shannon Divergence QC metric between components 2
and 3. NA_real_ for no-signal channels.
level"pixel" or "cell".
quantile_normLogical; whether quantile normalisation was applied.
histogramList with $breaks and $density vectors from
a pre-computed histogram of log2-transformed intensities. Used by
plot_distributions.
no_signalLogical; TRUE when the 3-component GMM failed and
the channel was fitted with a 2-component fallback, indicating no
detectable biological signal.
bicNamed numeric vector c(G2 = ..., G3 = ...) with mclust
BIC values (higher is better in mclust's convention) divided by the number
of points used to fit, giving a point-specific estimate comparable across
channels regardless of sample size. NULL for cell-level results.
Collapses the primary scene's channel list into a one-row-per-channel
data.frame for quick inspection. Multi-valued fields such as
color_rgb are rendered as strings.
channel_table(meta)channel_table(meta)
meta |
A |
A data.frame with one row per channel; zero rows when the
image has no channel metadata.
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) channel_table(metadata(img))path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) channel_table(metadata(img))
Low-level setter used by names<-.QPTIFFImage and direct
dimnames(img) <- assignments. Preserves the QPTIFFImage
class and the "metadata" attribute.
## S3 replacement method for class 'QPTIFFImage' dimnames(x) <- value## S3 replacement method for class 'QPTIFFImage' dimnames(x) <- value
x |
A |
value |
A list of length 3 ( |
x with updated dimnames.
Reverses log_transform: .
inv_log_transform(x, cofactor = 150)inv_log_transform(x, cofactor = 150)
x |
Numeric vector or matrix of log2-transformed values. |
cofactor |
Positive numeric cofactor used during |
Numeric vector or matrix on the original intensity scale.
x <- c(0, 100, 500, 2000, 65535) x_log <- log_transform(x) all.equal(x, inv_log_transform(x_log))x <- c(0, 100, 500, 2000, 65535) x_log <- log_transform(x) all.equal(x, inv_log_transform(x_log))
Computes JSD numerically on a dense grid, used as a signal-to-noise QC metric between the non-specific binding component (2) and the biological signal component (3) of the bgnorm model. Higher values indicate better separation and staining quality.
jsd_gaussians(mu1, sd1, mu2, sd2, n_grid = 2000L)jsd_gaussians(mu1, sd1, mu2, sd2, n_grid = 2000L)
mu1, mu2
|
Means of the two Gaussian distributions. |
sd1, sd2
|
Standard deviations of the two Gaussian distributions. |
n_grid |
Number of evaluation points (default 2000). |
Scalar JSD value in [0, 1] (bits, log2 base).
# Well-separated distributions -> high JSD jsd_gaussians(mu1 = 0, sd1 = 0.5, mu2 = 3, sd2 = 0.8) # Identical distributions -> JSD = 0 jsd_gaussians(mu1 = 1, sd1 = 1, mu2 = 1, sd2 = 1)# Well-separated distributions -> high JSD jsd_gaussians(mu1 = 0, sd1 = 0.5, mu2 = 3, sd2 = 0.8) # Identical distributions -> JSD = 0 jsd_gaussians(mu1 = 1, sd1 = 1, mu2 = 1, sd2 = 1)
Extracts the JSD between components 2 (non-specific binding) and 3
(signal) from the fitted model parameters stored in a
BgnormResult object.
jsd_qc(result)jsd_qc(result)
result |
A |
Named numeric scalar jsd.
set.seed(1) x <- c(exp(rnorm(300, log(300), 0.5)), exp(rnorm(100, log(2000), 0.9))) res <- bgnorm_cells(x) jsd_qc(res)set.seed(1) x <- c(exp(rnorm(300, log(300), 0.5)), exp(rnorm(100, log(2000), 0.9))) res <- bgnorm_cells(x) jsd_qc(res)
Number of channels in a QPTIFFImage
## S3 method for class 'QPTIFFImage' length(x)## S3 method for class 'QPTIFFImage' length(x)
x |
A |
Integer; the number of channels.
Applies to stabilise
variance in low-intensity fluorescence signals. The default cofactor of
150 is appropriate for 16-bit images from the Akoya PhenoCycler-Fusion;
adjust for other platforms.
log_transform(x, cofactor = 150)log_transform(x, cofactor = 150)
x |
Numeric vector or matrix of raw intensities. |
cofactor |
Positive numeric cofactor (default 150 for 16-bit images). |
Numeric vector or matrix of log2-transformed values.
x <- c(0, 100, 500, 2000, 10000, 65535) log_transform(x)x <- c(0, 100, 500, 2000, 10000, 65535) log_transform(x)
Uses EBImage::medianFilter (C-based, fast) when EBImage is
available, otherwise falls back to a pure-R sliding-window implementation.
median_filter_3x3(img)median_filter_3x3(img)
img |
Numeric matrix (height x width). |
Filtered matrix of the same dimensions.
m <- matrix(runif(100), 10, 10) m_filt <- median_filter_3x3(m)m <- matrix(runif(100), 10, 10) m_filt <- median_filter_3x3(m)
Retrieves the QPTIFFMetadata object (an OME-organised
hierarchy of slide / image / channel / scale metadata) stored inside a
QPTIFFImage. The generic falls back to
metadata for S4 objects such as
SummarizedExperiment, so metadata(img) and
metadata(spe) both work when bgnormR is loaded.
metadata(x, ...) ## S3 method for class 'QPTIFFImage' metadata(x, ...)metadata(x, ...) ## S3 method for class 'QPTIFFImage' metadata(x, ...)
x |
A |
... |
Unused. |
The returned object mirrors the OME hierarchy:
slideSlide-constant identity (id, barcode, operator, ...).
imagesOne scene per distinct image, each with
image_info (optics, camera, scan_resolution), channels
(per-channel name, fluorophore, dye_from_name,
exposure_time_us, wavelengths, color_rgb, ...) and
scales (per pyramid level).
acquisition_formatOne of "brightfield",
"polaris_scanband", "fusion_paged", "ome_tiff",
"ome_zarr", or "unknown".
Use the accessors (qpi_format, qpi_channels,
qpi_pixel_size_um, channel_table, ...) rather
than indexing the nested list directly.
A QPTIFFMetadata object (a named list).
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) meta <- metadata(img) qpi_format(meta) # Per-channel dye information as a tidy table channel_table(meta)[, c("name", "fluorophore", "dye_from_name")]path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) meta <- metadata(img) qpi_format(meta) # Per-channel dye information as a tidy table channel_table(meta)[, c("name", "fluorophore", "dye_from_name")]
Channel names of a QPTIFFImage (third-dimension names of the array)
## S3 method for class 'QPTIFFImage' names(x)## S3 method for class 'QPTIFFImage' names(x)
x |
A |
Character vector of channel names, or NULL.
Replaces the third-dimension names (channel names) of a
QPTIFFImage. Works for both eager (in-memory) and lazy (on-disk)
objects.
## S3 replacement method for class 'QPTIFFImage' names(x) <- value## S3 replacement method for class 'QPTIFFImage' names(x) <- value
x |
A |
value |
Character vector of length equal to the number of channels. |
x with updated channel names.
arr <- array(1:8, dim = c(2, 2, 2), dimnames = list(NULL, NULL, c("ch1", "ch2"))) img <- as.QPTIFFImage(arr) names(img) <- c("DAPI", "CD3") names(img)arr <- array(1:8, dim = c(2, 2, 2), dimnames = list(NULL, NULL, c("ch1", "ch2"))) img <- as.QPTIFFImage(arr) names(img) <- c("DAPI", "CD3") names(img)
Satisfies the DelayedArray seed contract (dim, dimnames,
type, extract_array) for a single pyramid level of an OME-Zarr
store, reading requested blocks through Rarr::read_zarr_array.
Zarr axes (typically c, y, x) are permuted to the QPTIFFImage
convention [y, x, c] = [H, W, C]. Do not construct directly;
use read_qptiff on a .ome.zarr store.
## S4 method for signature 'OMEZarrArraySeed' dim(x) ## S4 method for signature 'OMEZarrArraySeed' dimnames(x) ## S4 method for signature 'OMEZarrArraySeed' type(x) ## S4 method for signature 'OMEZarrArraySeed' show(object) ## S4 method for signature 'OMEZarrArraySeed' extract_array(x, index)## S4 method for signature 'OMEZarrArraySeed' dim(x) ## S4 method for signature 'OMEZarrArraySeed' dimnames(x) ## S4 method for signature 'OMEZarrArraySeed' type(x) ## S4 method for signature 'OMEZarrArraySeed' show(object) ## S4 method for signature 'OMEZarrArraySeed' extract_array(x, index)
x |
An |
object |
An |
index |
List of length 3; each element is |
extract_array returns an ordinary array; dim an integer
vector; dimnames a list; type a character scalar.
array_pathAbsolute path to the level array inside the store.
.dimInteger c(H, W, C) in output order.
.dimnamesList of dimnames; element 3 holds channel names.
dtype"integer" or "double".
axis_namesCharacter vector of zarr axis names (store order).
zarr_channelsInteger; 1-based zarr channel index per output channel.
divisorNumeric; value pixels are divided by in "double" mode.
metadataA QPTIFFMetadata object.
Plots a histogram of log2-transformed pixel (or cell) intensities and
overlays the component-wise density curves from the fitted GMM. One facet
per marker. Accepts a QPTIFFImage returned by
bgnorm_pixels (bgnorm results are carried as an attribute) or
a SummarizedExperiment /
SpatialExperiment with bgnorm results in
metadata(x)$bgnorm_results. Multi-sample
SpatialExperiments are automatically detected via sample_id
in colData and produce facets by sample_id x marker.
plot_distributions(x, results = NULL, markers = NULL, ncol = NULL)plot_distributions(x, results = NULL, markers = NULL, ncol = NULL)
x |
A |
results |
Optionally, the |
markers |
Character vector of markers to display, or |
ncol |
Number of facet columns. |
A ggplot object.
plot_qptiff, plot_pixel_classes,
bgnorm_pixels, bgnorm_sce
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) res <- bgnorm_pixels(img, sample_prop = 0.1) plot_distributions(res)path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) res <- bgnorm_pixels(img, sample_prop = 0.1) plot_distributions(res)
Displays the per-marker JSD quality metric as a clustered heatmap,
optionally annotated with the proportion of the signal GMM component.
Accepts a named list of BgnormResult objects (single sample),
a named list of such lists (multiple samples), or a
SummarizedExperiment /
SpatialExperiment with bgnorm results in
metadata(results)$bgnorm_results.
plot_jsd_heatmap( results, cluster_rows = TRUE, cluster_cols = TRUE, show_tissue_positivity = TRUE )plot_jsd_heatmap( results, cluster_rows = TRUE, cluster_cols = TRUE, show_tissue_positivity = TRUE )
results |
A named list of |
cluster_rows |
Logical; cluster samples (rows)? Default |
cluster_cols |
Logical; cluster markers (columns)? Default |
show_tissue_positivity |
Logical; overlay tissue positivity as circles
on the heatmap? Circle area is proportional to the tissue positivity
( |
A ggplot object.
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) res <- bgnorm_pixels(img, sample_prop = 0.1) plot_jsd_heatmap(res) # Multi-sample comparison (pass a named list) plot_jsd_heatmap(list(sample_A = res, sample_B = res))path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) res <- bgnorm_pixels(img, sample_prop = 0.1) plot_jsd_heatmap(res) # Multi-sample comparison (pass a named list) plot_jsd_heatmap(list(sample_A = res, sample_B = res))
Assigns each pixel/cell to its most probable GMM component and renders the
result. For a QPTIFFImage produced by
bgnorm_pixels: pseudocoloured raster (pixel-level data, three
components). For a
SpatialExperiment: scatter plot at spatial
coordinates using the bgnorm results from
metadata(spe)$bgnorm_results (two components). Multi-sample
SpatialExperiments are automatically detected via the
sample_id column in colData and faceted accordingly.
plot_pixel_classes( x, markers = NULL, resolution = 1L, point_size = 1, pixels = c(1024L, 1024L), flip_y = TRUE, large_data_threshold = 10000L, ncol = NULL )plot_pixel_classes( x, markers = NULL, resolution = 1L, point_size = 1, pixels = c(1024L, 1024L), flip_y = TRUE, large_data_threshold = 10000L, ncol = NULL )
x |
A |
markers |
Character vector of markers to display, or |
resolution |
Positive integer; pixel downsample factor for raster
output. Ignored for |
point_size |
Base point size for |
pixels |
Integer |
flip_y |
Logical; reverse y-axis for |
large_data_threshold |
Cell count threshold for automatic
scattermore use. Default |
ncol |
Number of facet columns. |
A ggplot object.
plot_qptiff, plot_distributions,
bgnorm_pixels, bgnorm_sce
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) res <- bgnorm_pixels(img, sample_prop = 0.1) plot_pixel_classes(res, markers = c("PanCK", "CD20"))path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) res <- bgnorm_pixels(img, sample_prop = 0.1) plot_pixel_classes(res, markers = c("PanCK", "CD20"))
For a QPTIFFImage: renders up to 10 channels as an additive
colour composite on a black background. Each channel is assigned a distinct
colour; its per-pixel intensity (min-max normalised to [0, 1]) drives the
channel's contribution - high-intensity pixels appear fully saturated while
low-intensity pixels are transparent (black). Channels that overlap in
space produce mixed additive colours (e.g. cyan + red -> white), matching
the standard composite view in FIJI / napari. When no markers are
provided the first 10 channels (by index) are displayed.
plot_qptiff( x, markers = NULL, resolution = 1L, palette = "magma", assay.type = "bgnorm", scale = c("marker", "sample", "none"), point_size = 1, pixels = c(1024L, 1024L), flip_y = TRUE, large_data_threshold = 10000L, ncol = NULL )plot_qptiff( x, markers = NULL, resolution = 1L, palette = "magma", assay.type = "bgnorm", scale = c("marker", "sample", "none"), point_size = 1, pixels = c(1024L, 1024L), flip_y = TRUE, large_data_threshold = 10000L, ncol = NULL )
x |
A |
markers |
Character vector of channel names to display, or |
resolution |
Positive integer; pixel downsample factor for
|
palette |
Viridis palette name used for |
assay.type |
Assay to visualise when |
scale |
Character; intensity scaling for
|
point_size |
Base point size for |
pixels |
Integer |
flip_y |
Logical; reverse y-axis for |
large_data_threshold |
Cell count above which scattermore is used
automatically. Default |
ncol |
Ignored for |
For a SpatialExperiment: plots each cell
at its spatial coordinates, coloured by its (per-channel scaled) intensity
from the requested assay using a viridis colour scale and dark theme. When
the cell count exceeds large_data_threshold and scattermore is
installed, the scatter layer is rasterised automatically.
A ggplot object.
plot_pixel_classes, plot_distributions,
bgnorm_pixels, bgnorm_sce
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) plot_qptiff(img, markers = c("PanCK", "CD20")) # After normalisation: plot background-adjusted intensities res <- bgnorm_pixels(img, sample_prop = 0.1) plot_qptiff(res, markers = c("PanCK", "CD20"), scale = "sample")path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) plot_qptiff(img, markers = c("PanCK", "CD20")) # After normalisation: plot background-adjusted intensities res <- bgnorm_pixels(img, sample_prop = 0.1) plot_qptiff(res, markers = c("PanCK", "CD20"), scale = "sample")
Summarise QC across multiple bgnorm results
qc_summary(results)qc_summary(results)
results |
A named list of |
A data.frame with columns name, jsd, and
prop_signal (proportion of pixels/cells in the signal component).
set.seed(2) mk1 <- bgnorm_cells(c(exp(rnorm(300,log(300),0.5)), exp(rnorm(100,log(2000),0.9)))) mk2 <- bgnorm_cells(c(exp(rnorm(400,log(250),0.4)), exp(rnorm( 80,log(1800),0.8)))) qc_summary(list(CD3 = mk1, CD8 = mk2))set.seed(2) mk1 <- bgnorm_cells(c(exp(rnorm(300,log(300),0.5)), exp(rnorm(100,log(2000),0.9)))) mk2 <- bgnorm_cells(c(exp(rnorm(400,log(250),0.4)), exp(rnorm( 80,log(1800),0.8)))) qc_summary(list(CD3 = mk1, CD8 = mk2))
Channel names of the primary scene
qpi_channel_names(meta)qpi_channel_names(meta)
meta |
A |
Character vector of channel names (possibly empty).
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") qpi_channel_names(metadata(read_qptiff(path)))path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") qpi_channel_names(metadata(read_qptiff(path)))
Per-channel metadata of the primary scene
qpi_channels(meta)qpi_channels(meta)
meta |
A |
A list of per-channel metadata lists (possibly empty).
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") length(qpi_channels(metadata(read_qptiff(path))))path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") length(qpi_channels(metadata(read_qptiff(path))))
Acquisition format of a parsed image
qpi_format(meta)qpi_format(meta)
meta |
A |
Character scalar, or NULL.
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") meta <- metadata(read_qptiff(path)) qpi_format(meta)path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") meta <- metadata(read_qptiff(path)) qpi_format(meta)
Is the primary scene a brightfield (RGB) image?
qpi_is_brightfield(meta)qpi_is_brightfield(meta)
meta |
A |
Logical scalar.
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") qpi_is_brightfield(metadata(read_qptiff(path)))path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") qpi_is_brightfield(metadata(read_qptiff(path)))
Number of pyramid levels of the primary scene
qpi_n_levels(meta)qpi_n_levels(meta)
meta |
A |
Integer scalar (at least 1).
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") qpi_n_levels(metadata(read_qptiff(path)))path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") qpi_n_levels(metadata(read_qptiff(path)))
Physical pixel size (microns) of the primary scene, full resolution
qpi_pixel_size_um(meta)qpi_pixel_size_um(meta)
meta |
A |
Numeric scalar, or NULL.
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") qpi_pixel_size_um(metadata(read_qptiff(path)))path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") qpi_pixel_size_um(metadata(read_qptiff(path)))
Primary scene of a parsed image (FullResolution if present, else the first)
qpi_primary_scene(meta)qpi_primary_scene(meta)
meta |
A |
A scene list, or NULL when no scenes are present.
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") scene <- qpi_primary_scene(metadata(read_qptiff(path))) scene$image_info$image_typepath <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") scene <- qpi_primary_scene(metadata(read_qptiff(path))) scene$image_info$image_type
An S4 class that satisfies the DelayedArray seed contract, enabling lazy, block-based processing of large Akoya PhenoCycler-Fusion QPTIFF images. Each channel (TIFF page) is read from disk only when data for that channel is actually requested by the DelayedArray framework.
Called internally by the DelayedArray framework. Reads only the
requested channels and the spatial region specified by index from
disk, keeping memory use proportional to the data actually needed.
## S4 method for signature 'QPTIFFArraySeed' dim(x) ## S4 method for signature 'QPTIFFArraySeed' dimnames(x) ## S4 method for signature 'QPTIFFArraySeed' type(x) ## S4 method for signature 'QPTIFFArraySeed' show(object) ## S4 method for signature 'QPTIFFArraySeed' extract_array(x, index)## S4 method for signature 'QPTIFFArraySeed' dim(x) ## S4 method for signature 'QPTIFFArraySeed' dimnames(x) ## S4 method for signature 'QPTIFFArraySeed' type(x) ## S4 method for signature 'QPTIFFArraySeed' show(object) ## S4 method for signature 'QPTIFFArraySeed' extract_array(x, index)
x |
A |
object |
A |
index |
List of length 3. Each element is either |
Internally the seed reads the full TIFF/BigTIFF directory chain at
construction time (fast: only header bytes, no pixel data) and stores
tile/strip layout metadata for each channel page. Pixel data is
fetched per-tile (when the image is tiled) or per-strip with
DEFLATE/zlib decompression handled by R's built-in memDecompress.
Unsupported compression types fall back to tiff::readTIFF.
Do not construct this class directly; use
read_qptiff(path, lazy = TRUE).
An ordinary array with dimensions
c(length(i_rows), length(i_cols), length(i_channels)).
filepathAbsolute path to the QPTIFF file.
.dimInteger vector [H, W, C].
.dimnamesList of dimnames; element 3 holds channel names.
page_layoutsList of per-channel page layout objects (one per channel) each containing the TIFF structural metadata needed to fetch that page.
dtypeCharacter, "integer" (raw 16-bit values 0-65535)
or "double" (normalised to [0, 1]).
metadataList; rich QPI metadata as returned by .parse_qpi_xml.
levelInteger; pyramid resolution level (1 = full resolution).
An S3 class representing a [H x W x C] multiplex image, either
eagerly loaded as a plain 3-D array or lazily backed by a
DelayedArray (see QPTIFFArraySeed-class). Created by
read_qptiff or as.QPTIFFImage, and consumed by
bgnorm_pixels, plot_qptiff, and
write_qptiff.
Not applicable; documents the QPTIFFImage class structure.
An S3-classed nested list returned by metadata on a
QPTIFFImage. It mirrors the OME hierarchy: a slide-constant
slide block, a list of image images (each with
image_info, channels, and scales), an
acquisition_format, and the raw source raw_xml. Prefer the
accessor helpers - qpi_format, qpi_channels,
qpi_channel_names, qpi_pixel_size_um,
qpi_n_levels, qpi_primary_scene,
qpi_is_brightfield, channel_table - over indexing
the nested list directly.
Not applicable; documents the QPTIFFMetadata structure.
Reads a multiplex image into R without Java, auto-detecting the container:
QPTIFF from Akoya PhenoCycler-Fusion (formerly CODEX), Cell DIVE, or Vectra / Polaris scanners - brightfield RGB, Polaris ScanBand XML, and Fusion per-page JSON+XML variants.
OME-TIFF (as written by tifffile, Bio-Formats, or QuPath) -
channel names are read from the <Channel Name="..."> attributes of
the OME-XML.
OME-Zarr (OME-NGFF) stores - requires the Rarr
package; channel names / colours are read from omero.channels.
read_qptiff(path, channels = NULL, level = 1L, as_integer = TRUE, lazy = FALSE)read_qptiff(path, channels = NULL, level = 1L, as_integer = TRUE, lazy = FALSE)
path |
Character, path to the image. A QPTIFF / OME-TIFF file, or
an OME-Zarr store (a directory, or a path ending in |
channels |
Character vector of channel names to load, an integer
vector of 1-based channel indices, or |
level |
Integer, pyramid resolution level. |
as_integer |
Logical; return raw 16-bit integers (0-65535) rather than
normalised |
lazy |
Logical; if |
Channel names and rich per-channel metadata (fluorophore, exposure time,
wavelengths, filters, colour) are parsed into an OME-organised
QPTIFFMetadata object, available via metadata.
The native, Java-free TIFF/QPTIFF reader and writer implemented here was translated from the bioio-tifffile fork by Rafael Tubelleza.
lazy = FALSEA QPTIFFImage - a 3-D numeric array
[height, width, channels] with class c("QPTIFFImage",
"array"). Channel names are stored in dimnames(img)[[3]].
Standard array subscripting works: img[, , "DAPI"] extracts a
single channel as a matrix; img[1:512, 1:512, ] crops spatially.
Rich metadata is in attr(img, "metadata").
lazy = TRUEA DelayedArray with
dim = c(H, W, C). Individual channel pages are read from disk
only when accessed. The seed is a QPTIFFArraySeed accessible via
DelayedArray::seed(arr).
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) dim(img) # c(H, W, n_channels) names(img) # channel names cd20 <- img[, , "CD20"] # extract one channel as a 2-D matrix # Load specific channels only img2 <- read_qptiff(path, channels = c("CD20", "PanCK")) names(img2) # Lazy / out-of-core load (backed by DelayedArray) arr <- read_qptiff(path, lazy = TRUE)path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) dim(img) # c(H, W, n_channels) names(img) # channel names cd20 <- img[, , "CD20"] # extract one channel as a 2-D matrix # Load specific channels only img2 <- read_qptiff(path, channels = c("CD20", "PanCK")) names(img2) # Lazy / out-of-core load (backed by DelayedArray) arr <- read_qptiff(path, lazy = TRUE)
Writes a QPTIFFImage as a multi-page 16-bit grayscale
OME-TIFF (one page per channel). The first page carries an OME-XML
ImageDescription (tag 270) that declares the pages as channels
(SizeC = nChannels, SizeZ = SizeT = 1), so viewers such as
QuPath / Bio-Formats interpret each page as a channel rather than a
timepoint. Per-channel bgnorm results are embedded so the file is
self-documenting and round-trips through read_qptiff.
write_qptiff(x, path)write_qptiff(x, path)
x |
A |
path |
Output file path (character scalar). The directory must exist. |
path, invisibly.
The QPTIFFImage stores background-adjusted
log-intensities. These are inverted with before
writing so the output values are in a linear intensity scale.
Written as-is; values are assumed
to be in [0, 65535].
All output values are rounded to the nearest integer and clamped to the
[0, 65535] range before being written as 16-bit unsigned integers.
The native, Java-free TIFF/QPTIFF reader and writer implemented here was translated from the bioio-tifffile fork by Rafael Tubelleza.
The first page's ImageDescription tag (TIFF tag 270) is an OME-XML
document. Its <Pixels> element declares SizeC = nChannels
with one <Channel Name="..."> per channel and an explicit
<TiffData> page-to-channel mapping, which is what makes downstream
viewers read the pages as channels. Channel 0's bgnorm metadata is appended
to the OME root as no-namespace elements (ignored by OME readers but read
back by read_qptiff); pages 1..n each carry a minimal
PerkinElmerQPI block:
<PerkinElmerQPI>
<Biomarker>CD20</Biomarker>
<transform>2^x</transform>
<bgnorm>{"level":"pixel","jsd":0.35,...}</bgnorm>
</PerkinElmerQPI>
The <Biomarker> element is the channel name; <bgnorm> holds a
JSON object with GMM parameters, JSD, threshold, and normalisation flags
(only present for bgnorm-adjusted images). If jsonlite is not
installed, the <bgnorm> element is omitted.
path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) res <- bgnorm_pixels(img, sample_prop = 0.1) out <- file.path(tempdir(), "PA_HNC_bgnorm.tif") write_qptiff(res, out) # Round-trip: channel names and dimensions are preserved img2 <- read_qptiff(out) names(img2) dim(img2)path <- system.file("extdata", "PA_HNC_sample.ome.tiff", package = "bgnormR") img <- read_qptiff(path) res <- bgnorm_pixels(img, sample_prop = 0.1) out <- file.path(tempdir(), "PA_HNC_bgnorm.tif") write_qptiff(res, out) # Round-trip: channel names and dimensions are preserved img2 <- read_qptiff(out) names(img2) dim(img2)