From 4bec3f124cf62928ceb23ea079a434c977b39adc Mon Sep 17 00:00:00 2001 From: SchulzDan Date: Fri, 24 Apr 2026 10:22:37 +0000 Subject: [PATCH 1/2] changes due to deprecation of scuttle aggregateAcrossCells --- DESCRIPTION | 6 +- NAMESPACE | 3 +- NEWS | 6 +- R/binAcrossPixels.R | 26 ++++-- R/plotSpotHeatmap.R | 105 ++++++++++++++----------- man/binAcrossPixels.Rd | 2 +- man/countInteractions.Rd | 4 +- man/plotSpotHeatmap.Rd | 2 +- man/readSCEfromTIFF.Rd | 9 ++- man/testInteractions.Rd | 28 +------ tests/testthat/test_binAcrossPixels.R | 3 +- tests/testthat/test_filterPixels.R | 3 +- tests/testthat/test_plotSpotHeatmap.R | 3 +- tests/testthat/test_readImagefromTXT.R | 20 +++-- tests/testthat/test_readSCEfromTXT.R | 14 ++-- 15 files changed, 127 insertions(+), 107 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index db877a61..966ecebb 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: imcRtools -Version: 1.17.1 +Version: 1.17.2 Title: Methods for imaging mass cytometry data analysis Description: This R package supports the handling and analysis of imaging mass cytometry @@ -35,7 +35,7 @@ Imports: SummarizedExperiment, methods, pheatmap, - scuttle, + scrapper, stringr, readr, EBImage, @@ -75,5 +75,5 @@ biocViews: ImmunoOncology, SingleCell, Spatial, DataImport, Clustering VignetteBuilder: knitr URL: https://github.com/BodenmillerGroup/imcRtools BugReports: https://github.com/BodenmillerGroup/imcRtools/issues -RoxygenNote: 7.3.2 +RoxygenNote: 7.3.3 Encoding: UTF-8 diff --git a/NAMESPACE b/NAMESPACE index 2c00d2ae..0c67b751 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -136,7 +136,8 @@ importFrom(pheatmap,pheatmap) importFrom(readr,cols) importFrom(readr,read_delim) importFrom(rlang,.data) -importFrom(scuttle,aggregateAcrossCells) +importFrom(scrapper,aggregateAcrossCells) +importFrom(scrapper,aggregateAcrossCells.se) importFrom(sf,st_area) importFrom(sf,st_buffer) importFrom(sf,st_cast) diff --git a/NEWS b/NEWS index d98772b0..13fe9bc1 100644 --- a/NEWS +++ b/NEWS @@ -264,4 +264,8 @@ Changes in version 1.15.5 (2025-10-20) Changes in version 1.17.1 (2026-03-30) -+ update function page/vignette \ No newline at end of file ++ update function page/vignette + +Changes in version 1.17.2 (2026-04-24) + ++ Replaced deprecated aggregateAcrossCells function from scuttle package with scrapper implementation \ No newline at end of file diff --git a/R/binAcrossPixels.R b/R/binAcrossPixels.R index ee963c31..ec3ad34d 100644 --- a/R/binAcrossPixels.R +++ b/R/binAcrossPixels.R @@ -34,12 +34,13 @@ #' # Visualizes heatmap after aggregation #' plotSpotHeatmap(sce) #' -#' @seealso \code{\link[scuttle]{aggregateAcrossCells}} for the aggregation +#' @seealso \code{\link[scrapper]{aggregateAcrossCells}} for the aggregation #' function #' #' @author Nils Eling (\email{nils.eling@@dqbm.uzh.ch}) #' -#' @importFrom scuttle aggregateAcrossCells +#' @importFrom scrapper aggregateAcrossCells +#' @importFrom scrapper aggregateAcrossCells.se #' @export binAcrossPixels <- function(object, bin_size, @@ -64,10 +65,23 @@ binAcrossPixels <- function(object, cur_df <- DataFrame(spot_id = object[[spot_id]], bin = unlist(cur_split)) - cur_out <- aggregateAcrossCells(object, cur_df, - statistics = statistic, - use.assay.type = assay_type, - ...) + cur_out <- aggregateAcrossCells.se(object, cur_df, + assay.type = assay_type, + ...) + + if (statistic == "mean") { + cur_counts <- assay(cur_out, "sums")/assay(cur_out, "detected") + assay(cur_out, "counts") <- cur_counts + } else if (statistic == "sum") { + assay(cur_out, "counts") <- assay(cur_out, "sums") + } else if (statistic == "median") { + cur_counts <- aggregateAcrossCells(assay(object, assay_type),factors = cur_df,compute.median = TRUE) + assay(cur_out, "counts") <- cur_counts$medians + } + cur_out <- as(cur_out,"SingleCellExperiment") + cur_out$spot_id <- cur_out$factor.spot_id + cur_out$bin <- cur_out$factor.bin + cur_out$ncells <- cur_out$counts return(cur_out) diff --git a/R/plotSpotHeatmap.R b/R/plotSpotHeatmap.R index 109f3538..71ee5060 100644 --- a/R/plotSpotHeatmap.R +++ b/R/plotSpotHeatmap.R @@ -58,13 +58,13 @@ #' plotSpotHeatmap(sce, log = FALSE, threshold = 200) #' #' @seealso \code{\link[pheatmap]{pheatmap}} for visual modifications -#' @seealso \code{\link[scuttle]{aggregateAcrossCells}} for the aggregation +#' @seealso \code{\link[scrapper]{aggregateAcrossCells}} for the aggregation #' function #' #' @author Nils Eling (\email{nils.eling@@dqbm.uzh.ch}) #' #' @importFrom pheatmap pheatmap -#' @importFrom scuttle aggregateAcrossCells +#' @importFrom scrapper aggregateAcrossCells #' @importFrom viridis viridis #' @importFrom stringr str_extract #' @importFrom SummarizedExperiment assay @@ -91,56 +91,65 @@ plotSpotHeatmap <- function(object, stop("'statistic' must be either 'median', 'mean' or 'sum'") } - cur_out <- aggregateAcrossCells(object, object[[spot_id]], - statistics = statistic, - use.assay.type = assay_type) + cur_out <- aggregateAcrossCells(assay(object,assay_type), + factors = list(spot_id = object[[spot_id]])) + + spot_names <- cur_out$combinations + + if (statistic == "mean") { + cur_out <- cur_out$sums/cur_out$detected + } else if (statistic == "sum") { + cur_out <- cur_out$sums + } else if (statistic == "median") { + cur_out <- cur_out$medians + } + + if (log) { + cur_mat <- log10(cur_out+1) + } else { + cur_mat <- cur_out + } + + if (!is.null(threshold)) { + cur_mat <- (cur_mat > threshold) * 1 - if (log) { - cur_mat <- log10(assay(cur_out, assay_type) + 1) - } else { - cur_mat <- assay(cur_out, assay_type) + if (is.na(breaks)) { + breaks <- c(0, 0.5, 1) } - if (!is.null(threshold)) { - cur_mat <- (cur_mat > threshold) * 1 - - if (is.na(breaks)) { - breaks <- c(0, 0.5, 1) - } - - if (is.na(legend_breaks)) { - legend_breaks <- c(0, 1) - } - - color <- c(color[1], color[length(color)]) + if (is.na(legend_breaks)) { + legend_breaks <- c(0, 1) } - colnames(cur_mat) <- cur_out[[spot_id]] - rownames(cur_mat) <- rowData(cur_out)[[channel_id]] + color <- c(color[1], color[length(color)]) + } + + colnames(cur_mat) <- rowData(object)$marker_name + rownames(cur_mat) <- rowData(object)[[channel_id]] + + # Order rows and cols based on spot metal + if (order_metals) { + cur_spots <- colnames(cur_mat) + cur_mass <- as.numeric(str_extract(cur_spots, "[0-9]{2,3}$")) + cur_spots <- cur_spots[order(cur_mass)] - # Order rows and cols based on spot metal - if (order_metals) { - cur_spots <- colnames(cur_mat) - cur_mass <- as.numeric(str_extract(cur_spots, "[0-9]{2,3}$")) - cur_spots <- cur_spots[order(cur_mass)] - - cur_channels <- rownames(cur_mat) - cur_mass <- as.numeric(str_extract(cur_channels, "[0-9]{2,3}")) - cur_channels <- cur_channels[order(cur_mass)] - cur_isotope <- str_extract(cur_channels, "[A-Za-z]{1,2}[0-9]{2,3}") - cur_rownames <- c(cur_channels[cur_isotope %in% cur_spots], - cur_channels[!cur_isotope %in% cur_spots]) - - cur_mat <- cur_mat[cur_rownames,cur_spots] - - cluster_cols <- FALSE - cluster_rows <- FALSE - } + cur_channels <- rownames(cur_mat) + cur_mass <- as.numeric(str_extract(cur_channels, "[0-9]{2,3}")) + cur_channels <- cur_channels[order(cur_mass)] + cur_isotope <- str_extract(cur_channels, "[A-Za-z]{1,2}[0-9]{2,3}") + cur_rownames <- c(cur_channels[cur_isotope %in% cur_spots], + cur_channels[!cur_isotope %in% cur_spots]) + + cur_mat <- cur_mat[cur_rownames,cur_spots] - # Transposed to match the CATALYST visualization - pheatmap(t(cur_mat), color = color, - cluster_cols = cluster_cols, - cluster_rows = cluster_rows, - breaks = breaks, legend_breaks = legend_breaks, - ...) -} + cluster_cols <- FALSE + cluster_rows <- FALSE + } + + # Transposed to match the CATALYST visualization + pheatmap(t(cur_mat), color = color, + cluster_cols = cluster_cols, + cluster_rows = cluster_rows, + breaks = breaks, legend_breaks = legend_breaks, + ...) +} \ No newline at end of file diff --git a/man/binAcrossPixels.Rd b/man/binAcrossPixels.Rd index 1ca5f833..d36a1e06 100644 --- a/man/binAcrossPixels.Rd +++ b/man/binAcrossPixels.Rd @@ -57,7 +57,7 @@ plotSpotHeatmap(sce) } \seealso{ -\code{\link[scuttle]{aggregateAcrossCells}} for the aggregation +\code{\link[scrapper]{aggregateAcrossCells}} for the aggregation function } \author{ diff --git a/man/countInteractions.Rd b/man/countInteractions.Rd index 9b579084..e02e13ee 100644 --- a/man/countInteractions.Rd +++ b/man/countInteractions.Rd @@ -72,8 +72,8 @@ fraction of cells of type A have at least a given number of neighbors of type B?" 4. \code{method = "interaction"}: The count is divided by the total number of -interactions from cell type A. The final count can be interpreted as the -fraction of interactions of cell type A that occur with cell type B. +interactions from cell type A. The final count can be interpreted as "What +fraction of interactions of cell type A occur with cell type B." } \examples{ diff --git a/man/plotSpotHeatmap.Rd b/man/plotSpotHeatmap.Rd index a7ae59a9..4bbb6584 100644 --- a/man/plotSpotHeatmap.Rd +++ b/man/plotSpotHeatmap.Rd @@ -100,7 +100,7 @@ plotSpotHeatmap(sce, log = FALSE, threshold = 200) \seealso{ \code{\link[pheatmap]{pheatmap}} for visual modifications -\code{\link[scuttle]{aggregateAcrossCells}} for the aggregation +\code{\link[scrapper]{aggregateAcrossCells}} for the aggregation function } \author{ diff --git a/man/readSCEfromTIFF.Rd b/man/readSCEfromTIFF.Rd index a4ecf242..be770b59 100644 --- a/man/readSCEfromTIFF.Rd +++ b/man/readSCEfromTIFF.Rd @@ -24,9 +24,11 @@ channels are stored as rows. } \description{ Helper function to process .tiff files created with the -steinbock pipeline into a \code{\linkS4class{SingleCellExperiment}} +steinbock pipeline \href{https://bodenmillergroup.github.io/steinbock/latest/} +into a \code{\linkS4class{SingleCellExperiment}} object. This function is mainly used to read-in data generated from a -"spillover slide". Here, each .tiff file contains the measurements of +"spillover slide" from the new XTi generation of IMC machines. +Here, each .tiff file contains the measurements of multiple pixels for a single stain across all open channels. } \section{Reading in .tiff files for spillover correction}{ @@ -65,6 +67,9 @@ sce \href{https://www.sciencedirect.com/science/article/pii/S1550413118306910}{Chevrier, S. et al. 2017. “Compensation of Signal Spillover in Suspension and Imaging Mass Cytometry.” Cell Systems 6: 612–20.} +\href{https://www.nature.com/articles/s41596-023-00881-0}{Windhager, +J. et al. 2023. “An end-to-end workflow for multiplexed image processing and analysis.” +Nature Protocols 18: 3565–3613.} } \author{ Victor Ibañez (\email{victor.ibanez@uzh.ch}) diff --git a/man/testInteractions.Rd b/man/testInteractions.Rd index cba13736..7e286a3e 100644 --- a/man/testInteractions.Rd +++ b/man/testInteractions.Rd @@ -56,31 +56,7 @@ are regarded as equal. Default taken from \code{all.equal}.} \item{BPPARAM}{parameters for parallelized processing.} } \value{ -a DataFrame containing one row per \code{group_by} entry and unique -\code{label} entry combination (\code{from_label}, \code{to_label}). The -object contains following entries: - -\itemize{ -\item{\code{ct}:}{ stores the interaction count as described in the details} -\item{\code{p_gt}:}{ stores the fraction of perturbations equal or greater -than \code{ct}} -\item{\code{p_lt}:}{ stores the fraction of perturbations equal or less than -\code{ct}} -\item{\code{interaction}:}{ is there the tendency for a positive interaction -(attraction) between \code{from_label} and \code{to_label}? Is \code{p_lt} -greater than \code{p_gt}?} -\item{\code{p}:}{ the smaller value of \code{p_gt} and \code{p_lt}.} -\item{\code{sig}:}{ is \code{p} smaller than \code{p_threshold}?} -\item{\code{sigval}:}{ Combination of \code{interaction} and \code{sig}.} -\itemize{ -\item{-1:}{ \code{interaction == FALSE} and \code{sig == TRUE}} -\item{0:}{ \code{sig == FALSE}} -\item{1:}{ \code{interaction == TRUE} and \code{sig == TRUE}} -} -} -\code{NA} is returned if a certain label is not present in this grouping -level. } \description{ Cell-cell interactions are summarized in different ways and @@ -113,8 +89,8 @@ fraction of cells of type A have at least a given number of neighbors of type B?" 4. \code{method = "interaction"}: The count is divided by the total number of -interactions from cell type A. The final count can be interpreted as the -fraction of interactions of cell type A that occur with cell type B. +interactions from cell type A. The final count can be interpreted as "What +fraction of interactions of cell type A occur with cell type B." } \section{Testing for significance}{ diff --git a/tests/testthat/test_binAcrossPixels.R b/tests/testthat/test_binAcrossPixels.R index eeb9a1e6..2a8fe0fb 100644 --- a/tests/testthat/test_binAcrossPixels.R +++ b/tests/testthat/test_binAcrossPixels.R @@ -2,7 +2,8 @@ test_that("binAcrossPixels function works.", { path <- system.file("extdata/spillover", package = "imcRtools") # Read in .txt - expect_silent(cur_sce <- readSCEfromTXT(path, verbose = FALSE)) + cur_sce <- readSCEfromTXT(path, verbose = FALSE) + expect_s4_class(cur_sce, "SingleCellExperiment") # Works expect_silent(out <- binAcrossPixels(cur_sce, bin_size = 10)) diff --git a/tests/testthat/test_filterPixels.R b/tests/testthat/test_filterPixels.R index 63721f8e..d9cedcde 100644 --- a/tests/testthat/test_filterPixels.R +++ b/tests/testthat/test_filterPixels.R @@ -2,7 +2,8 @@ test_that("filterPixels function works.", { path <- system.file("extdata/spillover", package = "imcRtools") # Read in .txt - expect_silent(cur_sce <- readSCEfromTXT(path, verbose = FALSE)) + cur_sce <- readSCEfromTXT(path, verbose = FALSE) + expect_s4_class(cur_sce, "SingleCellExperiment") assay(cur_sce, "exprs") <- asinh(counts(cur_sce)/5) bc_key <- as.numeric(unique(cur_sce$sample_mass)) diff --git a/tests/testthat/test_plotSpotHeatmap.R b/tests/testthat/test_plotSpotHeatmap.R index ac0e5121..6171acd7 100644 --- a/tests/testthat/test_plotSpotHeatmap.R +++ b/tests/testthat/test_plotSpotHeatmap.R @@ -2,7 +2,8 @@ test_that("plotSpotHeatmap function works.", { path <- system.file("extdata/spillover", package = "imcRtools") # Read in .txt - expect_silent(cur_sce <- readSCEfromTXT(path, verbose = FALSE)) + cur_sce <- readSCEfromTXT(path, verbose = FALSE) + expect_s4_class(cur_sce, "SingleCellExperiment") # Defaults work expect_silent(cur_out <- plotSpotHeatmap(cur_sce)) diff --git a/tests/testthat/test_readImagefromTXT.R b/tests/testthat/test_readImagefromTXT.R index 05377a8a..6bb4d395 100644 --- a/tests/testthat/test_readImagefromTXT.R +++ b/tests/testthat/test_readImagefromTXT.R @@ -1,10 +1,10 @@ test_that("readImagefromTXT function works.", { path <- system.file("extdata/mockData/raw", package = "imcRtools") - # Works - expect_silent(cur_cil <- readImagefromTXT(path)) - + # Works + cur_cil <- readImagefromTXT(path) expect_s4_class(cur_cil, "CytoImageList") + expect_equal(length(cur_cil), 3) expect_equal(channelNames(cur_cil), c("Ag107Di", "Pr141Di", "Sm147Di", "Eu153Di", "Yb172Di")) expect_equal(names(cur_cil), c("20210305_NE_mockData2_ROI_001_1", @@ -47,13 +47,16 @@ test_that("readImagefromTXT function works.", { test_2) # Read in individual files - expect_silent(cur_cil <- readImagefromTXT(path, pattern = "ROI_002")) + cur_cil <- readImagefromTXT(path, pattern = "ROI_002") + expect_s4_class(cur_cil, "CytoImageList") + expect_equal(length(cur_cil), 1) expect_equal(channelNames(cur_cil), c("Ag107Di", "Pr141Di", "Sm147Di", "Eu153Di", "Yb172Di")) expect_equal(names(cur_cil), c("20210305_NE_mockData2_ROI_002_2")) # Read in different channelNames - expect_silent(cur_cil <- readImagefromTXT(path, channel_pattern = "[A-Za-z]{2}[0-9]{3}")) + cur_cil <- readImagefromTXT(path, channel_pattern = "[A-Za-z]{2}[0-9]{3}") + expect_s4_class(cur_cil, "CytoImageList") expect_equal(length(cur_cil), 3) expect_equal(channelNames(cur_cil), c("Ag107", "Pr141", "Sm147", "Eu153", "Yb172")) expect_equal(names(cur_cil), c("20210305_NE_mockData2_ROI_001_1", @@ -61,7 +64,8 @@ test_that("readImagefromTXT function works.", { "20210305_NE_mockData2_ROI_003_3")) # Read in single channel - expect_silent(cur_cil <- readImagefromTXT(path, channel_pattern = "Ag107")) + cur_cil <- readImagefromTXT(path, channel_pattern = "Ag107") + expect_s4_class(cur_cil, "CytoImageList") expect_equal(length(cur_cil), 3) expect_equal(channelNames(cur_cil), c("Ag107")) expect_equal(names(cur_cil), c("20210305_NE_mockData2_ROI_001_1", @@ -75,8 +79,8 @@ test_that("readImagefromTXT function works.", { test_2) # parallelisation - expect_silent(cur_cil <- readImagefromTXT(path, - BPPARAM = BiocParallel::bpparam())) + cur_cil <- readImagefromTXT(path,BPPARAM = BiocParallel::bpparam()) + expect_s4_class(cur_cil, "CytoImageList") expect_equal(length(cur_cil), 3) expect_equal(channelNames(cur_cil), c("Ag107Di", "Pr141Di", "Sm147Di", "Eu153Di", "Yb172Di")) expect_equal(names(cur_cil), c("20210305_NE_mockData2_ROI_001_1", diff --git a/tests/testthat/test_readSCEfromTXT.R b/tests/testthat/test_readSCEfromTXT.R index bc72820b..cc2209e5 100644 --- a/tests/testthat/test_readSCEfromTXT.R +++ b/tests/testthat/test_readSCEfromTXT.R @@ -2,7 +2,8 @@ test_that("readSCEfromTXT function reads in correct objects.", { path <- system.file("extdata/spillover", package = "imcRtools") # Read in .txt - expect_silent(cur_sce <- readSCEfromTXT(path, verbose = FALSE)) + cur_sce <- readSCEfromTXT(path, verbose = FALSE) + expect_s4_class(cur_sce, "SingleCellExperiment") expect_equal(rowData(cur_sce)$channel_name, c("Dy161Di", "Dy162Di", "Dy163Di","Dy164Di")) expect_equal(rowData(cur_sce)$marker_name, c("Dy161", "Dy162", @@ -36,15 +37,17 @@ test_that("readSCEfromTXT function reads in correct objects.", { # Verbose output cur_out <- capture_output(cur_sce <- readSCEfromTXT(path)) - expect_equal(cur_out, "Spotted channels: Dy161, Dy162, Dy163, Dy164\nAcquired channels: Dy161, Dy162, Dy163, Dy164\nChannels spotted but not acquired: \nChannels acquired but not spotted: ") + expect_equal(gsub(".*\nSpotted","Spotted",cur_out), "Spotted channels: Dy161, Dy162, Dy163, Dy164\nAcquired channels: Dy161, Dy162, Dy163, Dy164\nChannels spotted but not acquired: \nChannels acquired but not spotted: ") # Other parameters - expect_silent(cur_sce_2 <- readSCEfromTXT(path, pattern = "Dy162", verbose = FALSE)) + cur_sce_2 <- readSCEfromTXT(path, pattern = "Dy162", verbose = FALSE) + expect_s4_class(cur_sce_2, "SingleCellExperiment") expect_equal(dim(cur_sce_2), c(4, 100)) expect_equal(rowData(cur_sce)$channel_name, c("Dy161Di", "Dy162Di", "Dy163Di","Dy164Di")) - expect_silent(cur_sce_2 <- readSCEfromTXT(path, metadata_cols = "X", verbose = FALSE)) + cur_sce_2 <- readSCEfromTXT(path, metadata_cols = "X", verbose = FALSE) + expect_s4_class(cur_sce_2, "SingleCellExperiment") expect_equal(counts(cur_sce), counts(cur_sce_2)) expect_equal(names(colData(cur_sce_2)), c("X", "sample_id", "sample_metal", "sample_mass" )) @@ -55,7 +58,8 @@ test_that("readSCEfromTXT function reads in correct objects.", { cur_files <- lapply(cur_files, read_delim, delim = "\t") names(cur_files) <- str_extract(cur_files_names, "[A-Za-z]{1,2}[0-9]{2,3}") - expect_silent(cur_sce_3 <- readSCEfromTXT(cur_files, verbose = FALSE)) + cur_sce_3 <- readSCEfromTXT(cur_files, verbose = FALSE) + expect_s4_class(cur_sce_3, "SingleCellExperiment") expect_equal(cur_sce, cur_sce_3) expect_silent(cur_sce_4 <- readSCEfromTXT(cur_files, metadata_cols = "X", verbose = FALSE)) expect_equal(cur_sce_2, cur_sce_4) From d80e11630257d09fda8b0135210438e4467309cc Mon Sep 17 00:00:00 2001 From: SchulzDan Date: Fri, 24 Apr 2026 15:41:24 +0000 Subject: [PATCH 2/2] fixed isses with new aggregateAcrossCells --- R/binAcrossPixels.R | 4 +- R/plotSpotHeatmap.R | 4 +- tests/testthat/Rplots.pdf | Bin 0 -> 27348 bytes tests/testthat/test_aggregateNeighbors.R | 97 +++++++++-------------- tests/testthat/test_plotSpotHeatmap.R | 10 --- 5 files changed, 42 insertions(+), 73 deletions(-) create mode 100644 tests/testthat/Rplots.pdf diff --git a/R/binAcrossPixels.R b/R/binAcrossPixels.R index ec3ad34d..529f75c3 100644 --- a/R/binAcrossPixels.R +++ b/R/binAcrossPixels.R @@ -70,8 +70,8 @@ binAcrossPixels <- function(object, ...) if (statistic == "mean") { - cur_counts <- assay(cur_out, "sums")/assay(cur_out, "detected") - assay(cur_out, "counts") <- cur_counts + cur_counts <- t(assay(cur_out, "sums"))/cur_out$counts + assay(cur_out, "counts") <- t(cur_counts) } else if (statistic == "sum") { assay(cur_out, "counts") <- assay(cur_out, "sums") } else if (statistic == "median") { diff --git a/R/plotSpotHeatmap.R b/R/plotSpotHeatmap.R index 71ee5060..69273ce5 100644 --- a/R/plotSpotHeatmap.R +++ b/R/plotSpotHeatmap.R @@ -95,9 +95,9 @@ plotSpotHeatmap <- function(object, factors = list(spot_id = object[[spot_id]])) spot_names <- cur_out$combinations - + #### check here if (statistic == "mean") { - cur_out <- cur_out$sums/cur_out$detected + cur_out <- t(cur_out$sums)/cur_out$counts } else if (statistic == "sum") { cur_out <- cur_out$sums } else if (statistic == "median") { diff --git a/tests/testthat/Rplots.pdf b/tests/testthat/Rplots.pdf new file mode 100644 index 0000000000000000000000000000000000000000..50ee3cdcb2b36d482c6222e63c61957ee90c8a69 GIT binary patch literal 27348 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