High-content imaging can capture detailed morphological and functional features on a single-cell level. It has been widely used in drug screen assays and for generating large-scale image datasets for machine learning models. Software such as CellProfiler extract features from high-content images for downstream analysis. CellProfiler implements a suite of traditional image processing algorithms to segment cells and extract about 1,000 morphological and texture features.
To simplify data analysis of CellProfiler features in R—many options exist in Python, such as PyCytominer—we developed an R/Bioconductor package. Our package leverages Bioconductor objects for batch correction and dimension reduction, lowering the learning curve for users familiar with single-cell RNA sequencing workflows.
Install this package.
Load packages.
Load CellProfiler output and create a
SingleCellExperiment object.
Prepare the data for machine learning.
# remove outlier images
sce <- removeOutliers(sce, min = 0, max = 300)
# remove outlier cells
stats <- perCellQCMetrics(sce, assay.type = "features")
sce <- sce[, !isOutlier(stats$sum)]
# remove features with zero spread
sce <- removeLowVariance(sce, robust = TRUE)
# remove zero-inflated features
sce <- removeZeroInflation(sce)Transform non-negatives features and scale all features.
Pseudo-bulk over images.
aggr <- scrapper::aggregateAcrossCells(
assay(sce, "tfmfeatures"),
factors = colData(sce)[, c("ImageNumber", "Drug", "Patient")]
)
sce_aggr <- SingleCellExperiment(
assays = list(sums = aggr$sums),
colData = DataFrame(aggr$combinations, ncells = aggr$counts)
)Use scater for exploratory data analysis. PCA on
pseudo-bulk features.
sce_aggr <- scater::runPCA(sce_aggr,
assay.type = "sums",
ncomponents = 10
)
scater::plotReducedDim(sce_aggr, dimred = "PCA", colour_by = "Patient")What is driving PC1?
UMAP.
sce_aggr <- scater::runUMAP(sce_aggr, exprs_values = "sums")
scater::plotUMAP(sce_aggr, colour_by = "Patient")Compare positive control to negative control.
set.seed(23)
sce$Drug <- as.factor(sce$Drug)
sce$Drug <- relevel(sce$Drug, ref = "D1")
types <- c("AreaShape", "Intensity", "Texture")
sce_single <- predictLOO(
sce,
target = "Drug", group = "Patient",
interest_level = "D7", reference_level = "D1",
types = types,
n_threads = 1
)Summarize feature subgroups in a volcano plot.
Individual scores.
Compare a list of drugs to negative control.
set.seed(23)
reference_level <- "D1"
interest_levels <- setdiff(levels(sce$Drug), reference_level)
sce_list <- map(interest_levels, function(interest_level) {
predictLOO(
sce,
target = "Drug", group = "Patient",
interest_level = interest_level, reference_level = reference_level,
types = types,
n_threads = 1
)
}, .progress = "drug screening")Custom volcano plot for summarizing drugs faceted by features.
# prepare stats table
p_adj_cutoff <- 0.05
fc_cutoff <- 0.5
tb_stats <- lapply(sce_list, calculateStats,
target = "Drug", group = "Patient"
) |>
bind_rows() |>
mutate(pvalue_adj = p.adjust(pvalue, method = "BH")) |>
mutate(Selection = ifelse(
pvalue_adj < p_adj_cutoff & log2FoldChange > fc_cutoff,
Target, ""
))
# volcano plot
tb_stats |>
ggplot(aes(log2FoldChange, -log10(pvalue_adj), label = Selection)) +
geom_vline(xintercept = c(0, fc_cutoff), alpha = 0.5, linetype = "dashed") +
geom_hline(
yintercept = c(0, -log10(p_adj_cutoff)), alpha = 0.5,
linetype = "dashed"
) +
geom_point(aes(color = Target)) +
geom_text_repel(aes(color = Target), max.overlaps = Inf) +
xlab("log2 fold change") +
ylab("-log10 p-value (BH adjusted)") +
facet_wrap(~Feature) +
ggtitle(paste0("False Discovery Rate: ", 100 * p_adj_cutoff, "%"))## R version 4.6.1 (2026-06-24)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 26.04 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] stats4 stats graphics grDevices utils datasets methods
## [8] base
##
## other attached packages:
## [1] ggrepel_0.9.8 dplyr_1.2.1
## [3] purrr_1.2.2 scrapper_1.7.3
## [5] scater_1.41.2 ggplot2_4.0.3
## [7] scuttle_1.23.1 cellpaintr_0.99.0
## [9] SingleCellExperiment_1.35.1 SummarizedExperiment_1.43.0
## [11] Biobase_2.73.1 GenomicRanges_1.65.1
## [13] Seqinfo_1.3.0 IRanges_2.47.2
## [15] S4Vectors_0.51.5 BiocGenerics_0.59.10
## [17] generics_0.1.4 MatrixGenerics_1.25.0
## [19] matrixStats_1.5.0 BiocStyle_2.41.0
##
## loaded via a namespace (and not attached):
## [1] gridExtra_2.3.1 rlang_1.3.0 magrittr_2.0.5
## [4] furrr_0.4.0 otel_0.2.0 compiler_4.6.1
## [7] vctrs_0.7.3 stringr_1.6.0 pkgconfig_2.0.3
## [10] crayon_1.5.3 fastmap_1.2.0 XVector_0.53.0
## [13] labeling_0.4.3 rmarkdown_2.31 tzdb_0.5.0
## [16] ggbeeswarm_0.7.3 bit_4.6.0 xfun_0.60
## [19] cachem_1.1.0 beachmat_2.29.0 jsonlite_2.0.0
## [22] DelayedArray_0.39.3 BiocParallel_1.47.0 irlba_2.3.7
## [25] parallel_4.6.1 R6_2.6.1 bslib_0.11.0
## [28] stringi_1.8.7 rsample_1.3.2 RColorBrewer_1.1-3
## [31] ranger_0.18.0 parallelly_1.48.0 jquerylib_0.1.4
## [34] Rcpp_1.1.2 knitr_1.51 FNN_1.1.4.1
## [37] readr_2.2.0 Matrix_1.7-5 tidyselect_1.2.1
## [40] abind_1.4-8 yaml_2.3.12 viridis_0.6.5
## [43] codetools_0.2-20 listenv_1.0.0 lattice_0.22-9
## [46] tibble_3.3.1 withr_3.0.3 S7_0.2.2
## [49] evaluate_1.0.5 future_1.70.0 pillar_1.11.1
## [52] BiocManager_1.30.27 vroom_1.7.1 hms_1.1.4
## [55] scales_1.4.0 globals_0.19.1 glue_1.8.1
## [58] maketools_1.3.2 tools_4.6.1 BiocNeighbors_2.7.2
## [61] sys_3.4.3 RSpectra_0.16-2 ScaledMatrix_1.21.0
## [64] buildtools_1.0.0 grid_4.6.1 yardstick_1.4.0
## [67] tidyr_1.3.2 beeswarm_0.4.0 BiocSingular_1.29.0
## [70] vipor_0.4.7 cli_3.6.6 rsvd_1.0.5
## [73] parsnip_1.6.0 S4Arrays_1.13.0 viridisLite_0.4.3
## [76] uwot_0.2.4 gtable_0.3.6 sass_0.4.10
## [79] digest_0.6.39 SparseArray_1.13.2 farver_2.1.2
## [82] htmltools_0.5.9 lifecycle_1.0.5 hardhat_1.4.3
## [85] sparsevctrs_0.3.6 bit64_4.8.2