@@ -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