From 61fca7d7ef4b3f8fcf8d518eab70f6f6dd967e4f Mon Sep 17 00:00:00 2001 From: Anagha Shenoy <90294056+anashen@users.noreply.github.com> Date: Mon, 27 Jul 2026 20:30:35 -0400 Subject: [PATCH 1/3] Add error handling for single-label groups during heatmap generation --- R/make_qc_heatmaps.R | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/R/make_qc_heatmaps.R b/R/make_qc_heatmaps.R index 3a1c2df..1e4acb8 100644 --- a/R/make_qc_heatmaps.R +++ b/R/make_qc_heatmaps.R @@ -50,11 +50,22 @@ make_QC_heatmap <- function( seurat_obj <- subset(seurat_obj,idents = names(which(table(Idents(seurat_obj))<=max.size))) } if (!is.null(n_downsample)) seurat_obj <- subset(seurat_obj, downsample=n_downsample) + + ident_groups <- table(Idents(seurat_obj)) + ident_groups <- ident_groups[ident_groups > 0] + if (length(ident_groups) < 2) { + stop("QC heatmaps require at least two identity groups with cells after filtering.") + } + mark_all <- FindAllMarkers(seurat_obj,only.pos = TRUE,min.pct = min.pct) mark_all %>% group_by(.data$cluster) %>% dplyr::filter(.data$avg_log2FC > logfc_cutoff) %>% slice_head(n = n_markers) %>% ungroup() -> top_markers + + if (nrow(top_markers) == 0) { + stop("No marker genes were identified for the requested identity groups.") + } if (!is.null(switch_id)) { Idents(seurat_obj) <- switch_id @@ -187,6 +198,12 @@ make_azimuth_QC_heatmaps <- function( for (level1 in names(result_list)) { lobj <- subset(object,cells = rownames(result_list[[level1]])) Idents(lobj) <- final_name + ident_groups <- table(Idents(lobj)) + ident_groups <- ident_groups[ident_groups > 0] + if (length(ident_groups) < 2) { + cli::cli_warn("Skipping {level1}; QC heatmaps require at least two final annotation groups with cells.") + next + } tryCatch({ plot_list[[level1]] <- make_QC_heatmap(lobj, min.size = min.final.group, identity = as.character(level1), ...) }, error = function(e) { From 01c1e4472d21462dcf5455ae85ef5e38d1ed85f4 Mon Sep 17 00:00:00 2001 From: Anagha Shenoy <90294056+anashen@users.noreply.github.com> Date: Mon, 27 Jul 2026 20:30:46 -0400 Subject: [PATCH 2/3] Add tests for QC heatmaps --- tests/testthat/test_visualization.R | 84 +++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 tests/testthat/test_visualization.R diff --git a/tests/testthat/test_visualization.R b/tests/testthat/test_visualization.R new file mode 100644 index 0000000..25a74f1 --- /dev/null +++ b/tests/testthat/test_visualization.R @@ -0,0 +1,84 @@ + +make_viz_test_object <- function(cells = NULL) { + query <- readRDS(test_path("test_obj.rds")) + query <- Seurat::NormalizeData(query, verbose = FALSE) + if (!is.null(cells)) { + query <- subset(query, cells = colnames(query)[seq_len(cells)]) + } + return(query) +} + +set_azimuth_metadata <- function(query, fine, broad, full) { + query@meta.data$azimuth_fine <- fine + query@meta.data$azimuth_broad <- broad + query@meta.data$full_hierarchical_labels <- full + query@meta.data$full_consistent_hierarchy <- TRUE + return(query) +} + +test_that("make_QC_heatmap rejects single-label inputs before marker detection", { + query <- make_viz_test_object() + query@meta.data$final_level_labels <- "Unassigned" + + expect_error( + make_QC_heatmap(query, group.by = "final_level_labels"), + "at least two identity groups", + fixed = TRUE + ) +}) + +test_that("make_azimuth_QC_heatmaps skips single-label final annotation groups", { + query <- make_viz_test_object() + query <- set_azimuth_metadata( + query, + fine = "Unassigned", + broad = "Unassigned", + full = "Unassigned" + ) + + expect_warning( + plots <- make_azimuth_QC_heatmaps(query, min.final.group = 10), + "Skipping Unassigned_1", + fixed = TRUE + ) + expect_false("Unassigned_1" %in% names(plots)) +}) + +test_that("make_azimuth_QC_heatmaps returns valid plots even when one group errors", { + query <- make_viz_test_object(cells = 70) + query <- set_azimuth_metadata( + query, + fine = c( + rep("Unassigned", 10), + rep("T cell", 10), + rep("B cell", 10), + rep("Fibroblast", 25), + rep("Rare stromal", 15) + ), + broad = c( + rep("Immune cell", 30), + rep("Stromal cell", 40) + ), + full = c( + rep("Immune cell|Lymphoid cell", 30), + rep("Stromal cell", 40) + ) + ) + + # after the filtering, the stromal cell group has only "Rare stromal" left, but this is a single-label group, so it will error out in make_QC_heatmap + # the valid plot for "Immune cell" should still be returned, and the error for "Stromal cell" should be caught and reported as a warning + expect_warning( + plots <- make_azimuth_QC_heatmaps( + query, + min.final.group = 9, + n_markers = 1, + max.size = 20 + ), + "Error in processing Stromal cell_1", + fixed = TRUE + ) + + expect_named(plots, "Immune_Lymphoid cell_1") + expect_s3_class(plots[["Immune_Lymphoid cell_1"]], "ggplot") + expect_false("Stromal cell_1" %in% names(plots)) +}) From 9c7e3b5f37f61d97a6a2d0ee955558e7e9d7ea83 Mon Sep 17 00:00:00 2001 From: Anagha Shenoy <90294056+anashen@users.noreply.github.com> Date: Mon, 27 Jul 2026 20:35:50 -0400 Subject: [PATCH 3/3] Bump version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 96b94f6..d4c5931 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: AzimuthAPI Title: Pan-Azimuth Web API Interface -Version: 1.0.0 +Version: 1.0.1 Authors@R: person("Satija", "Lab", email = "satijalabnygc@gmail.com", role = c("aut", "cre")) Description: An R package providing an interface to the Pan-Azimuth Web API for single-cell RNA sequencing analysis.