Skip to content

Commit bc8dd18

Browse files
authored
Merge pull request #1740 from bigomics/devel
New release
2 parents cbb60c0 + 1fb4a39 commit bc8dd18

15 files changed

Lines changed: 212 additions & 111 deletions

‎VERSION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v4.1.4+master260225
1+
v4.1.4+devel260317

‎components/app/R/global.R‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ if (Sys.info()["sysname"] != "Windows") {
2525
Sys.setenv("_R_CHECK_LENGTH_1_CONDITION_" = "true")
2626

2727

28-
options(shiny.maxRequestSize = 999 * 1024^2) ## max 999Mb upload
28+
options(shiny.maxRequestSize = 2048 * 1024^2) ## max 2GB (previously 999Mb) upload
2929
options(shiny.fullstacktrace = TRUE)
3030
# The following DT global options ensure
3131
# 1. The header scrolls with the X scroll bar

‎components/board.biomarker/R/biomarker_server.R‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,13 @@ BiomarkerBoard <- function(id, pgx) {
192192
)
193193
}
194194

195+
shiny::validate(
196+
shiny::need(
197+
!is.null(res),
198+
"Biomarker analysis could not be computed. The current filter settings result in too few features or samples. Please broaden your feature filter or sample selection."
199+
)
200+
)
201+
195202
is_computed(TRUE)
196203
return(res)
197204
})

‎components/board.clustering/R/clustering_server.R‎

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,22 @@ ClusteringBoard <- function(id, pgx, labeltype = shiny::reactive("feature")) {
141141
# reactive functions ##############
142142
shiny::observeEvent(
143143
{
144-
list(input$hm_splitby, pgx$X, pgx$samples)
144+
list(input$hm_splitby, input$hm_level, pgx$X, pgx$samples)
145145
},
146146
{
147-
shiny::req(pgx$X, pgx$samples, input$hm_splitby)
147+
shiny::req(pgx$X, pgx$samples, input$hm_splitby, input$hm_level)
148148
if (input$hm_splitby == "none") {
149149
return()
150150
}
151151
if (input$hm_splitby == "gene") {
152-
xgenes <- sort(rownames(getFilteredMatrix()$zx))
153-
shiny::updateSelectizeInput(session, "hm_splitvar", choices = xgenes, server = TRUE)
152+
if (input$hm_level == "geneset") {
153+
## at geneset level, show geneset names instead of genes
154+
xgenesets <- sort(rownames(getFilteredMatrix()$zx))
155+
shiny::updateSelectizeInput(session, "hm_splitvar", choices = xgenesets, server = TRUE)
156+
} else {
157+
xgenes <- sort(rownames(getFilteredMatrix()$zx))
158+
shiny::updateSelectizeInput(session, "hm_splitvar", choices = xgenes, server = TRUE)
159+
}
154160
}
155161
if (input$hm_splitby == "phenotype") {
156162
cvar <- sort(playbase::pgx.getCategoricalPhenotypes(pgx$samples, min.ncat = 2, max.ncat = 999))
@@ -178,6 +184,17 @@ ClusteringBoard <- function(id, pgx, labeltype = shiny::reactive("feature")) {
178184
shiny::updateRadioButtons(session, "hm_splitby", selected = "none")
179185
})
180186

187+
## update split radio button label to match current level
188+
shiny::observeEvent(input$hm_level, {
189+
shiny::req(input$hm_level)
190+
level_label <- tspan(input$hm_level, js = FALSE)
191+
choices <- c("none", "phenotype", "contrast", "gene")
192+
choices_names <- c("none", "phenotype", "contrast", level_label)
193+
names(choices) <- choices_names
194+
sel <- input$hm_splitby
195+
shiny::updateRadioButtons(session, "hm_splitby", choices = choices, selected = sel)
196+
})
197+
181198
shiny::observeEvent(pgx, {
182199
shiny::req(pgx$datatype)
183200
datatype <- pgx$datatype
@@ -377,8 +394,12 @@ ClusteringBoard <- function(id, pgx, labeltype = shiny::reactive("feature")) {
377394
splitby <- input$hm_splitby
378395
do.split <- splitby != "none"
379396

380-
if (splitby == "gene" && !splitvar %in% rownames(pgx$X)) {
381-
return(NULL)
397+
## validate splitvar exists in the appropriate matrix
398+
if (splitby == "gene") {
399+
split_rows <- if (input$hm_level == "geneset") rownames(pgx$gsetX) else rownames(pgx$X)
400+
if (!splitvar %in% split_rows) {
401+
return(NULL)
402+
}
382403
}
383404
if (splitby == "phenotype" && !splitvar %in% colnames(pgx$samples)) {
384405
return(NULL)
@@ -402,9 +423,10 @@ ClusteringBoard <- function(id, pgx, labeltype = shiny::reactive("feature")) {
402423
shiny::need(is.null(grp) || any(!is.na(grp)), "Selected grouping and filter combination is not valid (no samples left).")
403424
)
404425

405-
## split on gene expression value: hi vs. low
406-
if (do.split && splitvar %in% rownames(pgx$X)) {
407-
gx <- playbase::imputeMissing(pgx$X, method = "SVD2")
426+
## split on gene/geneset expression value: hi vs. low
427+
split_matrix <- if (input$hm_level == "geneset") pgx$gsetX else pgx$X
428+
if (do.split && splitvar %in% rownames(split_matrix)) {
429+
gx <- playbase::imputeMissing(split_matrix, method = "SVD2")
408430
gx <- gx[splitvar, colnames(zx)]
409431

410432
## TODO if this code is revived again, for some datasets
@@ -460,7 +482,7 @@ ClusteringBoard <- function(id, pgx, labeltype = shiny::reactive("feature")) {
460482
}
461483

462484
addsplitgene <- function(gg) {
463-
if (do.split && splitvar %in% rownames(pgx$X)) {
485+
if (do.split && splitvar %in% rownames(split_matrix)) {
464486
gg <- unique(c(splitvar, gg))
465487
}
466488
return(gg)

‎components/board.expression/R/expression_server.R‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,8 @@ ExpressionBoard <- function(id, pgx, labeltype = shiny::reactive("feature")) {
585585
if (length(j) == 0) {
586586
return(NULL)
587587
} else {
588-
gset <- names(which(pgx$GMT[j, ] != 0))
588+
gmt_rows <- as.matrix(pgx$GMT[j, , drop = FALSE])
589+
gset <- colnames(gmt_rows)[colSums(gmt_rows != 0, na.rm = TRUE) > 0]
589590
gset <- intersect(gset, rownames(pgx$gsetX))
590591
}
591592

‎components/board.featuremap/R/featuremap_plot_gset_sig.R‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ featuremap_plot_gset_sig_server <- function(id,
4444
shiny::req(pgx$X)
4545

4646
pos <- pgx$cluster.gsets$pos[["umap2d"]]
47+
shiny::validate(shiny::need( ## Custom species has this empty
48+
!is.null(pgx$cluster.gsets),
49+
"Cluster genesets are not available."
50+
))
4751
hilight <- NULL
4852
pheno <- "tissue"
4953
pheno <- sigvar()

‎components/board.featuremap/R/featuremap_plot_table_geneset_map.R‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ featuremap_plot_table_geneset_map_server <- function(id,
8989
ns <- session$ns
9090
sel.row <- 1
9191
getUMAP <- function() {
92+
shiny::validate(shiny::need( ## Custom species has this empty
93+
!is.null(pgx$cluster.gsets),
94+
"Cluster genesets are not available."
95+
))
9296
pos <- pgx$cluster.gsets$pos[["umap2d"]]
9397
colnames(pos) <- c("x", "y")
9498
pos

‎components/board.timeseries/R/module_features.R‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,12 @@ TimeSeriesBoard.features_server <- function(id,
172172
}
173173

174174
if (input$show_statdetails) {
175-
sel.p <- grep("^p[.]", colnames(kstats.full))
176-
sel.q <- grep("^q[.]", colnames(kstats.full))
175+
sel.p <- grep("^p(\\.|$)", colnames(kstats.full))
176+
sel.q <- grep("^q(\\.|$)", colnames(kstats.full))
177177
pq.tables <- kstats.full[, c(sel.p, sel.q), drop = FALSE]
178178
if (ik %in% names(pgx$gx.meta$meta)) {
179-
sel.p <- grep("^p[.]", colnames(ikstats.full))
180-
sel.q <- grep("^q[.]", colnames(ikstats.full))
179+
sel.p <- grep("^p(\\.|$)", colnames(ikstats.full))
180+
sel.q <- grep("^q(\\.|$)", colnames(ikstats.full))
181181
ik.pq.tables <- ikstats.full[, c(sel.p, sel.q), drop = FALSE]
182182
colnames(ik.pq.tables) <- paste0(colnames(ik.pq.tables), ".interaction")
183183
pq.tables <- cbind(pq.tables, ik.pq.tables)

‎components/board.upload/R/upload_module_computepgx.R‎

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,18 +1165,6 @@ upload_module_computepgx_server <- function(
11651165
sendSuccessMessageToUser = sendSuccessMessageToUser
11661166
)
11671167

1168-
path_to_params <- file.path(raw_dir(), "params.RData")
1169-
saveRDS(params, file = path_to_params)
1170-
1171-
# Normalize paths
1172-
script_path <- normalizePath(file.path(get_opg_root(), "bin", "pgxcreate_op.R"))
1173-
tmpdir <- normalizePath(raw_dir())
1174-
1175-
# Remove global variables
1176-
try(rm(annot_table))
1177-
try(rm(custom_geneset))
1178-
1179-
# Start the process and store it in the reactive value
11801168
shinyalert::shinyalert(
11811169
title = "Crunching your data!",
11821170
text = stringr::str_squish("Your dataset will be computed in the background.
@@ -1191,6 +1179,17 @@ upload_module_computepgx_server <- function(
11911179
selected = "load-tab"
11921180
)
11931181

1182+
path_to_params <- file.path(raw_dir(), "params.RData")
1183+
saveRDS(params, file = path_to_params)
1184+
1185+
# Normalize paths
1186+
script_path <- normalizePath(file.path(get_opg_root(), "bin", "pgxcreate_op.R"))
1187+
tmpdir <- normalizePath(raw_dir())
1188+
1189+
# Remove global variables
1190+
try(rm(annot_table))
1191+
try(rm(custom_geneset))
1192+
11941193
process_counter(process_counter() + 1)
11951194
dbg("[compute PGX process] : starting processx nr: ", process_counter())
11961195
dbg("[compute PGX process] : process tmpdir = ", tmpdir)

‎components/board.upload/R/upload_module_normalizationSC.R‎

Lines changed: 75 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ upload_module_normalizationSC_server <- function(id,
177177
shiny::req(r_samples())
178178
counts <- r_counts()
179179
samples <- r_samples()
180-
181180
kk <- intersect(colnames(counts), rownames(samples))
182181
counts <- counts[, kk, drop = FALSE]
183182
samples <- samples[kk, , drop = FALSE]
@@ -187,54 +186,22 @@ upload_module_normalizationSC_server <- function(id,
187186
if (ncol(counts) > cells_trs) {
188187
dbg("[normalizationSC_server:ds_norm_Counts:] Random sampling of:", cells_trs, "cells.")
189188
kk <- sample(colnames(counts), cells_trs)
190-
counts <- counts[, kk, drop = FALSE]
189+
## Character subsetting on a large dgCMatrix can hit Matrix dispatch issues;
190+
## use integer indices (via match) which always work reliably for sparse matrices.
191+
counts <- counts[, match(kk, colnames(counts)), drop = FALSE]
191192
samples <- samples[kk, , drop = FALSE]
192193
}
193194

194195
if (input$ref_atlas != "<select>") {
195-
dbg("[normalizationSC_server:ds_norm_Counts:] Inferring cell types with Azimuth!")
196196
dbg("[normalizationSC_server:ds_norm_Counts:] Reference atlas:", input$ref_atlas)
197197
counts <- as(counts, "dgCMatrix")
198-
shiny::withProgress(message = "Inferring cell types with Azimuth...", value = 0.1, {
199-
azm <- playbase::pgx.runAzimuth(counts = counts, reference = input$ref_atlas)
200-
dbg("[normalizationSC_server:ds_norm_Counts:] Cell types inferred.")
201-
})
202-
203-
if (is.null(azm)) {
204-
message_text <- paste(
205-
"Please select the Azimuth reference atlas that fits your data.",
206-
"NOTE: The uploaded data might NOT be single cell data. Please check your input matrix."
207-
)
208-
shiny::validate(
209-
shiny::need(FALSE, message_text)
210-
)
211-
}
212-
213-
if (class(azm) %in% c("matrix", "data.frame")) {
214-
kk <- grep("^predicted.*l*2$", colnames(azm))
215-
if (any(kk)) {
216-
celltype.azimuth <- azm[, kk]
217-
if (length(unique(celltype.azimuth)) > 15) {
218-
kk <- grep("^predicted.*l*1$", colnames(azm))
219-
celltype.azimuth <- azm[, kk]
220-
}
221-
} else {
222-
kk <- grep("^predicted.*subclass*$", colnames(azm))
223-
celltype.azimuth <- azm[, kk]
224-
if (length(unique(celltype.azimuth)) > 15) {
225-
kk <- grep("^predicted.class*$", colnames(azm))
226-
celltype.azimuth <- azm[, kk]
227-
}
228-
}
229-
samples <- cbind(samples, celltype.azimuth = celltype.azimuth)
230-
} else if (is.vector(azm)) {
231-
dbg("[normalizationSC_server:ds_norm_Counts] Azimuth atlas might be incorrect. Please double check.")
232-
samples <- cbind(samples, celltype.azimuth = azm)
233-
}
234198

235-
## Normalization & Dimensional reduction
199+
## logCPM + dim-reduction first: these run fine in the main process and
200+
## their intermediates are freed before the subprocess starts.
236201
dbg("[normalizationSC_server] Performing logCPM normalization...")
237-
nX <- playbase::logCPM(as.matrix(counts), prior = 1, total = 1e4)
202+
counts_dense <- as.matrix(counts)
203+
nX <- playbase::logCPM(counts_dense, prior = 1, total = 1e4)
204+
rm(counts_dense)
238205
jj <- head(order(-matrixStats::rowSds(nX, na.rm = TRUE)), 250)
239206
nX1 <- nX[jj, , drop = FALSE]
240207
nX1 <- nX1 - rowMeans(nX1, na.rm = TRUE)
@@ -247,21 +214,77 @@ upload_module_normalizationSC_server <- function(id,
247214
pos.list[["umap"]] <- uwot::umap(t(nX1), n_neighbors = max(2, nb))
248215
pos.list <- lapply(pos.list, function(x) {
249216
rownames(x) <- colnames(nX1)
250-
return(x)
217+
x
251218
})
252219
})
220+
rm(nX, nX1)
221+
gc()
253222
dbg("[normalizationSC_server] PCA, tSNE & UMAP completed.")
254223

255-
dbg("[normalizationSC_server] Creating & preprocessing Seurat object..")
256-
options(Seurat.object.assay.calcn = TRUE)
257-
getOption("Seurat.object.assay.calcn")
258-
counts <- as(counts, "dgCMatrix")
259-
shiny::withProgress(message = "Creating & preprocessing Seurat object...", value = 0.9, {
260-
SO <- playbase::pgx.createSeuratObject(counts, samples,
261-
batch = NULL, filter = FALSE, preprocess = FALSE
224+
## Run Azimuth + Seurat in a single fresh subprocess.
225+
## Large h5ad uploads leave glibc's heap fragmented after deallocation;
226+
## any subsequent Seurat C++ call (ScaleData, FindNeighbors, RunPCA) then
227+
## crashes. One callr subprocess isolates both Azimuth and the Seurat
228+
## pipeline from the main process heap.
229+
dbg("[normalizationSC_server] Running Azimuth + Seurat in subprocess...")
230+
shiny::withProgress(message = "Inferring cell types & building Seurat object...", value = 0.7, {
231+
result <- callr::r(
232+
function(counts, samples, reference, lib) {
233+
.libPaths(lib)
234+
options(Seurat.object.assay.calcn = TRUE)
235+
236+
## Azimuth cell type annotation
237+
azm <- tryCatch(
238+
playbase::pgx.runAzimuth(counts = counts, reference = reference),
239+
error = function(e) {
240+
message("[callr] pgx.runAzimuth failed: ", conditionMessage(e))
241+
NULL
242+
}
243+
)
244+
245+
## Add cell type column to samples metadata
246+
if (!is.null(azm)) {
247+
if (is.data.frame(azm) || is.matrix(azm)) {
248+
kk <- grep("^predicted.*l*2$", colnames(azm))
249+
if (any(kk)) {
250+
celltype.azimuth <- azm[, kk]
251+
if (length(unique(celltype.azimuth)) > 15) {
252+
kk <- grep("^predicted.*l*1$", colnames(azm))
253+
celltype.azimuth <- azm[, kk]
254+
}
255+
} else {
256+
kk <- grep("^predicted.*subclass*$", colnames(azm))
257+
celltype.azimuth <- azm[, kk]
258+
if (length(unique(celltype.azimuth)) > 15) {
259+
kk <- grep("^predicted.class*$", colnames(azm))
260+
celltype.azimuth <- azm[, kk]
261+
}
262+
}
263+
samples <- cbind(samples, celltype.azimuth = celltype.azimuth)
264+
} else if (is.vector(azm)) {
265+
samples <- cbind(samples, celltype.azimuth = azm)
266+
}
267+
}
268+
269+
SO <- playbase::pgx.createSeuratObject(counts, samples,
270+
batch = NULL, filter = FALSE, preprocess = FALSE
271+
)
272+
SO <- playbase::seurat.preprocess(SO, sct = FALSE, tsne = FALSE, umap = FALSE)
273+
list(SO = SO, azm_ok = !is.null(azm))
274+
},
275+
args = list(counts = counts, samples = samples, reference = input$ref_atlas, lib = .libPaths())
262276
)
263-
SO <- playbase::seurat.preprocess(SO, sct = FALSE, tsne = FALSE, umap = FALSE)
264277
})
278+
279+
if (!result$azm_ok) {
280+
message_text <- paste(
281+
"Please select the Azimuth reference atlas that fits your data.",
282+
"NOTE: The uploaded data might NOT be single cell data. Please check your input matrix."
283+
)
284+
shiny::validate(shiny::need(FALSE, message_text))
285+
}
286+
287+
SO <- result$SO
265288
dbg("[normalizationSC_server] Seurat object created & preprocessed.")
266289
kk <- setdiff(colnames(samples), colnames(SO@meta.data))
267290
if (length(kk) > 1) {
@@ -274,7 +297,7 @@ upload_module_normalizationSC_server <- function(id,
274297
pos.tsne = pos.list[["tsne"]],
275298
pos.umap = pos.list[["umap"]]
276299
)
277-
rm(counts, nX, nX1, samples, SO)
300+
rm(counts, samples, SO)
278301
return(LL)
279302
} else {
280303
shinyalert::shinyalert(
@@ -484,7 +507,7 @@ upload_module_normalizationSC_server <- function(id,
484507

485508
X <- shiny::reactive({
486509
shiny::req(r_counts())
487-
X <- playbase::logCPM(as.matrix(r_counts()), total = 1e4, prior = 1)
510+
X <- playbase::logCPM(r_counts(), total = 1e4, prior = 1)
488511
return(X)
489512
})
490513

0 commit comments

Comments
 (0)