OSCR

Dual tumour-myeloid targeting of glioblastoma with GPNMB CAR-T cells.

Code ↔ Paper

5 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 5 matches
  1. [1] § scRNA-seq atlas of GPNMB+ CNS myeloid cells ↔ 03_gpnmb_scRNA_methods_reproducibility.Rmd, lines 436–561 · score 0.69 · Kruskal Wallis, polarization state, BH, M0, pairwise, M1
  2. [2] § GPNMB supports MES states and immune recruitment ↔ 01_export_gpnmb_wtko_publishable.Rmd, lines 340–393 · score 0.59 · FindMarkers, Seurat, enrichment, Neftel, DEGs, OPC
  3. [3] § GPNMB supports MES states and immune recruitment ↔ 03_gpnmb_scRNA_methods_reproducibility.Rmd, lines 300–355 · score 0.59 · FindMarkers, Seurat, enrichment, Neftel, DEGs, OPC
  4. [4] § Methods › GSEA and enrichment mapping ↔ 03_gpnmb_scRNA_methods_reproducibility.Rmd, lines 300–355 · score 0.52 · enrichment scores, GSEA, regenerated, pathway, filtering, RNA
  5. [5] § scRNA-seq atlas of GPNMB+ CNS myeloid cells ↔ 03_gpnmb_scRNA_methods_reproducibility.Rmd, lines 436–561 · score 0.51 · polarization states, simplified, signatures, M1, M2, GPNMB expression

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

R Markdown · 607 lines · 27 KB · no license · 4 matches

  1. ---
  2. title: "GPNMB scRNA-seq methods reproducibility notebook"
  3. output:
  4. html_document:
  5. toc: true
  6. toc_float: false
  7. ---
  8. ```{r setup, include=FALSE}
  9. knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE)
  10. ```
  11. ## 1. Inputs and provenance
  12. ```{r libraries}
  13. suppressPackageStartupMessages({
  14. library(Seurat)
  15. library(Matrix)
  16. library(parallel)
  17. library(foreach)
  18. library(doParallel)
  19. library(dplyr)
  20. library(tidyr)
  21. library(tibble)
  22. library(readr)
  23. library(readxl)
  24. library(stringr)
  25. library(ggplot2)
  26. library(patchwork)
  27. library(fgsea)
  28. })
  29. ```
  30. ```{r helpers}
  31. submission_dir <- normalizePath(getwd(), winslash = "/", mustWork = TRUE)
  32. analysis_dir <- normalizePath(file.path(submission_dir, ".."), winslash = "/", mustWork = TRUE)
  33. output_dir <- file.path(submission_dir, "outputs")
  34. stats_dir <- file.path(output_dir, "stats")
  35. fig_dir <- file.path(output_dir, "figures")
  36. dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
  37. dir.create(stats_dir, recursive = TRUE, showWarnings = FALSE)
  38. dir.create(fig_dir, recursive = TRUE, showWarnings = FALSE)
  39. worker_count <- max(1L, parallel::detectCores() - 1L)
  40. doParallel::registerDoParallel(cores = worker_count)
  41. pick_first_match <- function(x, candidates) {
  42. hit <- candidates[candidates %in% x]
  43. if (length(hit) == 0) {
  44. return(NA_character_)
  45. }
  46. hit[[1]]
  47. }
  48. pick_feature_name <- function(object, candidates) {
  49. feat <- rownames(object)
  50. hit <- feat[toupper(feat) %in% toupper(candidates)]
  51. if (length(hit) == 0) {
  52. return(NA_character_)
  53. }
  54. hit[[1]]
  55. }
  56. pick_umap_reduction <- function(object, preferred = character()) {
  57. reduction_names <- names(object@reductions)
  58. preferred_hit <- preferred[preferred %in% reduction_names]
  59. if (length(preferred_hit) > 0) {
  60. return(preferred_hit[[1]])
  61. }
  62. umap_hit <- reduction_names[grepl("umap", reduction_names, ignore.case = TRUE)]
  63. if (length(umap_hit) > 0) {
  64. return(umap_hit[[1]])
  65. }
  66. two_dim_hit <- reduction_names[vapply(
  67. reduction_names,
  68. function(reduction_name) {
  69. embedding <- tryCatch(SeuratObject::Embeddings(object[[reduction_name]]), error = function(e) NULL)
  70. !is.null(embedding) && ncol(embedding) >= 2
  71. },
  72. logical(1)
  73. )]
  74. if (length(two_dim_hit) > 0) {
  75. return(two_dim_hit[[1]])
  76. }
  77. stop("No UMAP-like or other 2D reduction found in the Seurat object.")
  78. }
  79. save_plot_pair <- function(plot_handle, stem, width = 8, height = 6) {
  80. ggsave(filename = paste0(stem, ".pdf"), plot = plot_handle, width = width, height = height, units = "in")
  81. ggsave(filename = paste0(stem, ".png"), plot = plot_handle, width = width, height = height, units = "in", dpi = 300)
  82. }
  83. extract_named_list <- function(x) {
  84. if (is.list(x) && !is.data.frame(x)) {
  85. out <- lapply(x, function(y) unique(as.character(stats::na.omit(y))))
  86. out <- out[lengths(out) > 0]
  87. return(out)
  88. }
  89. if (is.data.frame(x)) {
  90. out <- lapply(x, function(y) unique(as.character(stats::na.omit(y))))
  91. out <- out[lengths(out) > 0]
  92. return(out)
  93. }
  94. stop("Could not coerce the gene set container into a named list.")
  95. }
  96. wide_df_to_named_list <- function(df) {
  97. out <- lapply(df, function(col) {
  98. col <- as.character(col)
  99. col <- col[!is.na(col) & nzchar(col)]
  100. unique(col)
  101. })
  102. out[lengths(out) > 0]
  103. }
  104. normalize_condition <- function(existing, barcode, sample_id) {
  105. out <- rep(NA_character_, length(barcode))
  106. if (!is.null(existing)) {
  107. out <- as.character(existing)
  108. }
  109. joined <- paste(barcode, sample_id, out)
  110. out[grepl("gpnmb|ko", joined, ignore.case = TRUE)] <- "KO"
  111. out[grepl("ctrl|wt|gl261", joined, ignore.case = TRUE)] <- "WT"
  112. out[grepl("pbs|sham", joined, ignore.case = TRUE)] <- "Sham"
  113. out
  114. }
  115. extract_named_vector <- function(x, cell_ids) {
  116. if (is.atomic(x) && !is.null(names(x))) {
  117. return(x[names(x) %in% cell_ids])
  118. }
  119. if (is.data.frame(x)) {
  120. id_col <- pick_first_match(colnames(x), c("cell_id", "cell", "barcode", "sample", "rownames"))
  121. value_col <- pick_first_match(colnames(x), c("state", "subtype", "label", "assignment", "class"))
  122. if (!is.na(id_col) && !is.na(value_col)) {
  123. out <- x[[value_col]]
  124. names(out) <- x[[id_col]]
  125. return(out[names(out) %in% cell_ids])
  126. }
  127. }
  128. if (is.list(x)) {
  129. for (element in x) {
  130. current <- tryCatch(extract_named_vector(element, cell_ids), error = function(e) NULL)
  131. if (!is.null(current) && length(current) > 0) {
  132. return(current)
  133. }
  134. }
  135. }
  136. NULL
  137. }
  138. run_safe_fgsea <- function(pathways, stats_tbl) {
  139. stats_vec <- stats_tbl$avg_log2FC
  140. names(stats_vec) <- stats_tbl$gene
  141. stats_vec <- stats_vec[!is.na(stats_vec)]
  142. stats_vec <- stats_vec[!duplicated(names(stats_vec))]
  143. stats_vec <- sort(stats_vec, decreasing = TRUE)
  144. fgsea::fgsea(pathways = pathways, stats = stats_vec, minSize = 5, maxSize = 500)
  145. }
  146. ```
  147. ```{r manifest}
  148. manifest <- tibble::tribble(
  149. ~domain, ~label, ~path, ~notes,
  150. "tumor", "Local tumor-side Seurat object", file.path(analysis_dir, "GPNMB_seurat_object_GL261_and_myeloid_111024.rds"), "Primary local tumor object for reproducible release",
  151. "tumor", "Legacy Reboot2 upstream object", "C:/Users/n mikolajewicz/Dropbox/PDF Projects - JM/Data/scRNA-seq/01_sci-RNA-seq3_Hong_Kevin_Jason/NM_HH/Data/Preprocessed_Datasets/R727_M02_HH_SET4_GL261_Gpnmb_reboot_rPCA_integration_240322.Rdata", "Documented provenance from Reboot2",
  152. "tumor", "Legacy GPNMB_02 upstream object", "R760_M01_NM2_r18_Neil_Gpnmb_Mm_081023.Rdata", "Documented provenance from GPNMB_02",
  153. "tumor", "Reference atlas", file.path(analysis_dir, "R726_M02_HH_SET1_allGBM_reboot_rPCA_integration_ANNOTATED_220422.rds"), "Documented provenance",
  154. "myeloid", "Integrated myeloid object", file.path(analysis_dir, "seurat_all_myeloid_160523.rds"), "Primary local myeloid object for reproducible release",
  155. "myeloid", "Myeloid NMF object", file.path(analysis_dir, "NMF_myeloid_v1_160523.rds"), "Program context",
  156. "myeloid", "Inferred-state object", file.path(analysis_dir, "ZM_myeloid_inferred_states.rds"), "State context",
  157. "myeloid", "GPNMB CDI object", file.path(analysis_dir, "ZMyeloid_03_CDI_GPNMB_Myeloid_050524.rds"), "Associated genes"
  158. )
  159. readr::write_tsv(manifest, file.path(stats_dir, "methods_object_manifest.tsv"))
  160. manifest
  161. ```
  162. ## 2. GL261 WT vs KO tumor object overview
  163. ```{r load-tumor}
  164. tumor_object_path <- manifest$path[manifest$label == "Local tumor-side Seurat object"]
  165. stopifnot(file.exists(tumor_object_path))
  166. so_tumor_all <- readRDS(tumor_object_path)
  167. tumor_umap <- pick_umap_reduction(so_tumor_all)
  168. tumor_meta <- [email hidden] %>%
  169. tibble::rownames_to_column("cell_id")
  170. tumor_barcode_col <- pick_first_match(colnames(tumor_meta), c("Barcode", "barcode", "sample", "orig.ident"))
  171. tumor_sample_col <- pick_first_match(colnames(tumor_meta), c("sample", "Barcode", "orig.ident"))
  172. tumor_condition_col <- pick_first_match(colnames(tumor_meta), c("condition", "Condition"))
  173. tumor_class_col <- pick_first_match(colnames(tumor_meta), c("class", "Class"))
  174. tumor_subclass_col <- pick_first_match(colnames(tumor_meta), c("subclass", "Subclass"))
  175. tumor_meta$barcode_id <- if (!is.na(tumor_barcode_col)) as.character(tumor_meta[[tumor_barcode_col]]) else tumor_meta$cell_id
  176. tumor_meta$sample_id <- if (!is.na(tumor_sample_col)) as.character(tumor_meta[[tumor_sample_col]]) else tumor_meta$barcode_id
  177. tumor_meta$condition_clean <- normalize_condition(
  178. existing = if (!is.na(tumor_condition_col)) tumor_meta[[tumor_condition_col]] else NULL,
  179. barcode = tumor_meta$barcode_id,
  180. sample_id = tumor_meta$sample_id
  181. )
  182. tumor_meta$class_clean <- if (!is.na(tumor_class_col)) as.character(tumor_meta[[tumor_class_col]]) else NA_character_
  183. tumor_meta$subclass_clean <- if (!is.na(tumor_subclass_col)) as.character(tumor_meta[[tumor_subclass_col]]) else NA_character_
  184. tumor_meta$is_tumor <- if (!is.na(tumor_class_col)) {
  185. grepl("tumor|gbm|glia", tumor_meta$class_clean, ignore.case = TRUE)
  186. } else {
  187. TRUE
  188. }
  189. so_tumor_all$condition_clean <- tumor_meta$condition_clean
  190. so_tumor_all$class_clean <- tumor_meta$class_clean
  191. so_tumor_all$subclass_clean <- tumor_meta$subclass_clean
  192. so_tumor_all$is_tumor <- tumor_meta$is_tumor
  193. tumor_gene_feature <- pick_feature_name(so_tumor_all, c("GPNMB", "Gpnmb"))
  194. tumor_state_cols <- intersect(
  195. c("gbm.index", "Richards_Developmental", "Richards_Injury_Response", "Neftel_AC", "Neftel_MES1", "Neftel_MES2", "Neftel_OPC", "Neftel_NPC1", "Neftel_NPC2"),
  196. colnames(tumor_meta)
  197. )
  198. tumor_overview <- tibble::tribble(
  199. ~field, ~value,
  200. "Tumor object", basename(tumor_object_path),
  201. "UMAP reduction", tumor_umap,
  202. "Condition column", ifelse(is.na(tumor_condition_col), "derived from barcode/sample", tumor_condition_col),
  203. "Class column", ifelse(is.na(tumor_class_col), "not present", tumor_class_col),
  204. "Subclass column", ifelse(is.na(tumor_subclass_col), "not present", tumor_subclass_col),
  205. "Tumor-state columns detected", paste(tumor_state_cols, collapse = ", ")
  206. )
  207. readr::write_tsv(tumor_overview, file.path(stats_dir, "methods_tumor_overview.tsv"))
  208. tumor_overview
  209. ```
  210. ## 3. Tumor UMAP reproduction with key annotations
  211. ```{r tumor-umaps}
  212. p_tumor_condition <- DimPlot(so_tumor_all, reduction = tumor_umap, group.by = "condition_clean", raster = FALSE) +
  213. ggtitle("Tumor-side object", subtitle = "WT / KO / sham condition")
  214. save_plot_pair(p_tumor_condition, file.path(fig_dir, "methods_tumor_umap_condition"), width = 8, height = 6)
  215. if (!is.na(tumor_class_col)) {
  216. p_tumor_class <- DimPlot(so_tumor_all, reduction = tumor_umap, group.by = "class_clean", raster = FALSE, label = TRUE, repel = TRUE) +
  217. ggtitle("Tumor-side object", subtitle = "Class annotation")
  218. save_plot_pair(p_tumor_class, file.path(fig_dir, "methods_tumor_umap_class"), width = 9, height = 7)
  219. }
  220. if (!is.na(tumor_subclass_col)) {
  221. p_tumor_subclass <- DimPlot(so_tumor_all, reduction = tumor_umap, group.by = "subclass_clean", raster = FALSE, label = FALSE) +
  222. ggtitle("Tumor-side object", subtitle = "Subclass annotation")
  223. save_plot_pair(p_tumor_subclass, file.path(fig_dir, "methods_tumor_umap_subclass"), width = 10, height = 8)
  224. }
  225. if (!is.na(tumor_gene_feature)) {
  226. p_tumor_gpnmb <- FeaturePlot(so_tumor_all, reduction = tumor_umap, features = tumor_gene_feature, raster = FALSE) +
  227. ggtitle("Tumor-side object", subtitle = paste0(tumor_gene_feature, " expression"))
  228. save_plot_pair(p_tumor_gpnmb, file.path(fig_dir, "methods_tumor_umap_gpnmb"), width = 8, height = 6)
  229. }
  230. ```
  231. ## 4. WT vs KO Neftel / GBM-state summaries and sample-level statistics
  232. ```{r tumor-state-summary}
  233. if (length(tumor_state_cols) > 0) {
  234. tumor_state_cell <- tumor_meta %>%
  235. dplyr::filter(is_tumor, condition_clean %in% c("WT", "KO")) %>%
  236. dplyr::select(cell_id, sample_id, barcode_id, condition_clean, dplyr::all_of(tumor_state_cols)) %>%
  237. tidyr::pivot_longer(cols = dplyr::all_of(tumor_state_cols), names_to = "state", values_to = "score")
  238. } else {
  239. tumor_state_cell <- tibble::tibble()
  240. }
  241. if (nrow(tumor_state_cell) > 0) {
  242. tumor_state_sample <- tumor_state_cell %>%
  243. dplyr::group_by(sample_id, barcode_id, condition_clean, state) %>%
  244. dplyr::summarise(score_mean = mean(score, na.rm = TRUE), score_median = median(score, na.rm = TRUE), n_cells = dplyr::n(), .groups = "drop")
  245. tumor_state_stats <- tumor_state_sample %>%
  246. dplyr::group_by(state) %>%
  247. dplyr::summarise(
  248. p_value_t_test = tryCatch(t.test(score_mean ~ condition_clean)$p.value, error = function(e) NA_real_),
  249. wt_samples = dplyr::n_distinct(sample_id[condition_clean %in% "WT"]),
  250. ko_samples = dplyr::n_distinct(sample_id[condition_clean %in% "KO"]),
  251. wt_cells = sum(n_cells[condition_clean %in% "WT"], na.rm = TRUE),
  252. ko_cells = sum(n_cells[condition_clean %in% "KO"], na.rm = TRUE),
  253. .groups = "drop"
  254. )
  255. readr::write_tsv(tumor_state_sample, file.path(stats_dir, "methods_tumor_state_sample_level.tsv"))
  256. readr::write_tsv(tumor_state_stats, file.path(stats_dir, "methods_tumor_state_stats.tsv"))
  257. }
  258. ```
  259. ## 5. ED2-style GSEA ranking and plot regeneration
  260. ```{r tumor-gsea}
  261. so_tumor_only <- so_tumor_all[, tumor_meta$is_tumor & tumor_meta$condition_clean %in% c("WT", "KO")]
  262. so_tumor_only$condition_clean <- factor(tumor_meta$condition_clean[tumor_meta$is_tumor & tumor_meta$condition_clean %in% c("WT", "KO")])
  263. Idents(so_tumor_only) <- so_tumor_only$condition_clean
  264. if (length(unique(as.character(Idents(so_tumor_only)))) >= 2) {
  265. tumor_deg <- Seurat::FindMarkers(
  266. object = so_tumor_only,
  267. ident.1 = "WT",
  268. ident.2 = "KO",
  269. test.use = "wilcox",
  270. logfc.threshold = 0,
  271. min.pct = 0
  272. )
  273. tumor_deg$gene <- rownames(tumor_deg)
  274. tumor_deg <- tumor_deg %>% tibble::as_tibble() %>% dplyr::arrange(desc(avg_log2FC))
  275. readr::write_tsv(tumor_deg, file.path(stats_dir, "methods_tumor_wilcox_deg.tsv"))
  276. if (requireNamespace("scMiko", quietly = TRUE)) {
  277. gene_sets_raw <- scMiko::geneSets[["GBM_Hs_Neftel2019"]]
  278. gene_sets <- extract_named_list(gene_sets_raw)
  279. keep <- intersect(names(gene_sets), c("AC", "MES1", "MES2", "OPC", "NPC1", "NPC2", "Neftel_AC", "Neftel_MES1", "Neftel_MES2", "Neftel_OPC", "Neftel_NPC1", "Neftel_NPC2"))
  280. gene_sets <- gene_sets[keep]
  281. names(gene_sets) <- gsub("^Neftel_", "", names(gene_sets))
  282. gsea_tbl <- run_safe_fgsea(gene_sets, tumor_deg) %>%
  283. tibble::as_tibble() %>%
  284. dplyr::arrange(padj, pval)
  285. readr::write_tsv(gsea_tbl, file.path(stats_dir, "methods_tumor_neftel_gsea.tsv"))
  286. ranked_stats <- tumor_deg$avg_log2FC
  287. names(ranked_stats) <- tumor_deg$gene
  288. ranked_stats <- ranked_stats[!duplicated(names(ranked_stats))]
  289. ranked_stats <- sort(ranked_stats, decreasing = TRUE)
  290. for (current_pathway in intersect(c("AC", "MES1", "MES2", "OPC", "NPC1", "NPC2"), names(gene_sets))) {
  291. this_row <- gsea_tbl %>% dplyr::filter(pathway == current_pathway)
  292. p <- fgsea::plotEnrichment(gene_sets[[current_pathway]], ranked_stats) +
  293. labs(
  294. title = paste0("ED2-style WT vs KO GSEA: ", current_pathway),
  295. subtitle = if (nrow(this_row) > 0) {
  296. paste0("NES = ", signif(this_row$NES[[1]], 3), "; p = ", signif(this_row$pval[[1]], 3))
  297. } else {
  298. "FGSEA result"
  299. },
  300. x = "Ranked differential-expression statistic",
  301. y = "Enrichment score"
  302. ) +
  303. theme_bw()
  304. save_plot_pair(p, file.path(fig_dir, paste0("methods_ed2_neftel_gsea_", tolower(current_pathway))), width = 7, height = 5)
  305. }
  306. }
  307. }
  308. ```
  309. ## 6. Myeloid meta-atlas overview
  310. ```{r load-myeloid}
  311. myeloid_object_path <- manifest$path[manifest$label == "Integrated myeloid object"]
  312. stopifnot(file.exists(myeloid_object_path))
  313. so_myeloid <- readRDS(myeloid_object_path)
  314. # Prefer the stored BBKNN embedding ("b") for the integrated myeloid object.
  315. myeloid_umap <- pick_umap_reduction(so_myeloid, preferred = "b")
  316. myeloid_meta <- [email hidden] %>%
  317. tibble::rownames_to_column("cell_id")
  318. myeloid_sample_col <- pick_first_match(colnames(myeloid_meta), c("clean.id2", "clean.id", "sample", "Barcode", "orig.ident"))
  319. myeloid_study_col <- pick_first_match(colnames(myeloid_meta), c("study", "Study"))
  320. myeloid_etiology_col <- pick_first_match(colnames(myeloid_meta), c("etiology", "type", "diagnosis", "PR", "PR2"))
  321. myeloid_state_col <- pick_first_match(colnames(myeloid_meta), c("subtype", "state", "label", "assignment", "class", "G1"))
  322. myeloid_meta$sample_id <- if (!is.na(myeloid_sample_col)) as.character(myeloid_meta[[myeloid_sample_col]]) else myeloid_meta$cell_id
  323. myeloid_meta$study_clean <- if (!is.na(myeloid_study_col)) as.character(myeloid_meta[[myeloid_study_col]]) else "unspecified_study"
  324. myeloid_meta$etiology_clean <- if (!is.na(myeloid_etiology_col)) as.character(myeloid_meta[[myeloid_etiology_col]]) else "unspecified_etiology"
  325. myeloid_meta$state_clean <- if (!is.na(myeloid_state_col)) as.character(myeloid_meta[[myeloid_state_col]]) else NA_character_
  326. state_object_path <- manifest$path[manifest$label == "Inferred-state object"]
  327. if (file.exists(state_object_path)) {
  328. inferred_states <- readRDS(state_object_path)
  329. inferred_vector <- extract_named_vector(inferred_states, myeloid_meta$cell_id)
  330. if (!is.null(inferred_vector) && length(inferred_vector) > 0) {
  331. myeloid_meta$state_clean[match(names(inferred_vector), myeloid_meta$cell_id)] <- as.character(inferred_vector)
  332. }
  333. }
  334. so_myeloid$sample_id <- myeloid_meta$sample_id
  335. so_myeloid$study_clean <- myeloid_meta$study_clean
  336. so_myeloid$etiology_clean <- myeloid_meta$etiology_clean
  337. so_myeloid$state_clean <- myeloid_meta$state_clean
  338. myeloid_gene_feature <- pick_feature_name(so_myeloid, c("GPNMB", "Gpnmb"))
  339. myeloid_overview <- tibble::tribble(
  340. ~field, ~value,
  341. "Myeloid object", basename(myeloid_object_path),
  342. "UMAP reduction", myeloid_umap,
  343. "Study column", ifelse(is.na(myeloid_study_col), "not present", myeloid_study_col),
  344. "Etiology column", ifelse(is.na(myeloid_etiology_col), "not present", myeloid_etiology_col),
  345. "State column", ifelse(is.na(myeloid_state_col), "supplemented from inferred-state object when available", myeloid_state_col)
  346. )
  347. readr::write_tsv(myeloid_overview, file.path(stats_dir, "methods_myeloid_overview.tsv"))
  348. myeloid_overview
  349. ```
  350. ## 7. Myeloid UMAP reproduction with key annotations
  351. ```{r myeloid-umaps}
  352. if (!is.na(myeloid_study_col)) {
  353. p_myeloid_study <- DimPlot(so_myeloid, reduction = myeloid_umap, group.by = "study_clean", raster = FALSE) +
  354. ggtitle("Myeloid meta-atlas", subtitle = "Study / cohort")
  355. save_plot_pair(p_myeloid_study, file.path(fig_dir, "methods_myeloid_umap_study"), width = 10, height = 8)
  356. }
  357. if (!is.na(myeloid_etiology_col)) {
  358. p_myeloid_etiology <- DimPlot(so_myeloid, reduction = myeloid_umap, group.by = "etiology_clean", raster = FALSE) +
  359. ggtitle("Myeloid meta-atlas", subtitle = "Etiology / diagnosis")
  360. save_plot_pair(p_myeloid_etiology, file.path(fig_dir, "methods_myeloid_umap_etiology"), width = 10, height = 8)
  361. }
  362. if (any(!is.na(myeloid_meta$state_clean))) {
  363. p_myeloid_state <- DimPlot(so_myeloid, reduction = myeloid_umap, group.by = "state_clean", raster = FALSE) +
  364. ggtitle("Myeloid meta-atlas", subtitle = "Inferred state / subtype")
  365. save_plot_pair(p_myeloid_state, file.path(fig_dir, "methods_myeloid_umap_state"), width = 10, height = 8)
  366. }
  367. if (!is.na(myeloid_gene_feature)) {
  368. p_myeloid_gpnmb <- FeaturePlot(so_myeloid, reduction = myeloid_umap, features = myeloid_gene_feature, raster = FALSE) +
  369. ggtitle("Myeloid meta-atlas", subtitle = paste0(myeloid_gene_feature, " expression"))
  370. save_plot_pair(p_myeloid_gpnmb, file.path(fig_dir, "methods_myeloid_umap_gpnmb"), width = 8, height = 6)
  371. }
  372. ```
  373. ## 8. GPNMB expression across polarization states with manuscript-faithful tests
  374. ```{r myeloid-polarization}
  375. marker_dir <- "C:/Users/n mikolajewicz/Dropbox/PDF Projects - JM/Data/scRNA-seq/01_sci-RNA-seq3_Hong_Kevin_Jason/NM_HH/PR_GBM/Marker tables"
  376. smart_path <- file.path(marker_dir, "Smart_Signature.tsv")
  377. orecchioni_path <- file.path(marker_dir, "Orecchioni_2019_polarization_states.xlsx")
  378. buscher_path <- file.path(marker_dir, "Buscher_2017_polarization_states.xlsx")
  379. composite_polarization_cell <- tibble::tibble()
  380. composite_polarization_sample <- tibble::tibble()
  381. summary_tbl <- tibble::tibble()
  382. pairwise_tbl <- tibble::tibble()
  383. if (!is.na(myeloid_gene_feature) &&
  384. file.exists(smart_path) &&
  385. file.exists(orecchioni_path) &&
  386. file.exists(buscher_path) &&
  387. requireNamespace("scMiko", quietly = TRUE)) {
  388. smart.list <- wide_df_to_named_list(read.delim(smart_path) %>% dplyr::select(-tidyselect::any_of("X")))
  389. Orecchioni.2019.list <- wide_df_to_named_list(readxl::read_xlsx(orecchioni_path, "Sheet1"))
  390. Buscher.2017.list <- wide_df_to_named_list(readxl::read_xlsx(buscher_path, "Sheet1"))
  391. Jablonski.2015.list <- list(
  392. Jablonski.2015.M0 = c("Sh2d3c", "Slc13a3", "Rcan1", "4632428N05Rik", "Trp53inp1", "Nr1d2", "Fcgrt", "Slc40a1", "Nfxl1", "Il16"),
  393. Jablonski.2015.M1 = c("Cd38", "Cfb", "Slfn4", "H2-Q6", "Fpr1", "Slfn1", "Gpr18", "Ccrl2", "Fpr2", "Cxcl10", "Mpa2l", "Oasl1", "Tlr2", "Ms4a4c", "LOC100503664", "Irak3", "Hp", "Itgal", "Herc6", "Cd300lf", "Isf20", "Pstpip2", "Cp", "Isg15", "Probe 1452408_at", "E030037K03Rik", "Saa3", "Ifit1", "Marco", "F11r", "Rsad2", "Ddx60", "Pilr1", "Cpd", "Fam26f", "Aoah", "Gngt2", "Mx1", "Pyhin1", "Epb4.1l3", "Slfn8", "Arhgap24", "Nfkbiz", "Gbp6", "Stat1", "Zpb1", "D14Erd668e", "Ddx58", "Tuba4a", "Nfkbiz", "H2-T10", "Ebi3", "Stat1", "Fam176b", "Xaf1", "Gbp6", "Stat2", "Sepx1", "Ifit2"),
  394. Jablonski.2015.M2 = c("Ptgs1", "Egr2", "Olfm1", "Flrt2", "P2ry1", "Vwf", "Bcar3", "Il6st", "Tanc2", "Mmp12", "Tcfec", "Clec7a", "Matk", "Myc", "Clec10a", "Amz1", "Tmem158", "Tiam1", "Rhoj", "Mmp9", "Mrc1", "Atp6v0a1", "Lmna", "Chst7", "Atp6v0d2", "Gnb4", "Emp2", "Cd300ld", "Cd83", "Socs6", "Actn1", "Plk2", "Ptpla")
  395. )
  396. Colombo.2024.list <- list(
  397. Colombo.2024.M1 = c("Irf1", "Gbp5", "Batf2", "Gbp2", "Irgm1", "Igtp", "Gbp3", "Nampt", "Serpina3g", "Gbp7", "Cxcl10", "Nod1", "Gbp6", "Tap1", "Parp9", "Gbp9", "Casp4", "Gbp4", "Irgm2", "Pla2g4a", "Nlrc5", "Sp140", "Peli1", "Serpina3f", "Slco3a1", "Casp1", "Mlkl", "Il27"),
  398. Colombo.2024.M2 = c("Tmem26", "Slc7a2", "Arg1", "Smap2", "Flt1", "Chil3", "Mgl2", "Flrt2", "Cblb", "Ak2", "Irf4", "Klf4", "Nfil3", "Ap2m1", "Rnf19b", "Cish", "Batf3", "Il1rl2", "Plekhf1", "Mcf2l")
  399. )
  400. master.set <- c(smart.list, Jablonski.2015.list, Orecchioni.2019.list, Colombo.2024.list, Buscher.2017.list)
  401. master.set <- lapply(master.set, toupper)
  402. match.list <- list(
  403. Ghosh.2023 = c("Ghosh.2023.M1", "Ghosh.2023.M0", "Ghosh.2023.M2"),
  404. Becker.2015 = c("Becker.2015.M1", "Becker.2015.M2"),
  405. Bell.2016 = c("Bell.2016.M1", "Bell.2016.M2"),
  406. Coates.2008 = c("Coates.2008.M1", "Coates.2008.M2"),
  407. Martinez.2006 = c("Martinez.2006.M1", "Martinez.2006.M2"),
  408. LM22 = c("LM22.M0", "LM22.M1", "LM22.M2"),
  409. Murray.2017 = c("Murray.2017.M1", "Murray.2017.M2"),
  410. Jablonski.2015 = c("Jablonski.2015.M0", "Jablonski.2015.M1", "Jablonski.2015.M2"),
  411. Orecchioni.2019 = c("Orecchioni.2019.M1.invitro", "Orecchioni.2019.M2.invitro"),
  412. Buscher.2017 = c("Buscher.2017.M1.invivo", "Buscher.2017.M2.invivo"),
  413. Colombo.2024 = c("Colombo.2024.M1", "Colombo.2024.M2")
  414. )
  415. split_by <- if ("clean.id2" %in% colnames([email hidden])) "clean.id2" else "sample_id"
  416. so.hs <- SplitObject(so_myeloid, split.by = split_by)
  417. ms.res.list <- list()
  418. for (sname in names(so.hs)) {
  419. object <- so.hs[[sname]]
  420. ms.result <- scMiko::runMS(object = object, genelist = master.set, return.plots = FALSE, scale = FALSE)
  421. df.res <- ms.result[["data"]] %>% dplyr::select(-tidyselect::any_of("class.ms"))
  422. expr_df <- Seurat::FetchData(object, vars = myeloid_gene_feature)
  423. colnames(expr_df) <- "expr"
  424. df.res <- bind_cols(df.res, expr_df)
  425. df.res$sample_id <- sname
  426. ms.res.list[[sname]] <- df.res
  427. }
  428. df.expr <- bind_rows(ms.res.list)
  429. match.list <- lapply(match.list, function(x) x[x %in% colnames(df.expr)])
  430. match.list <- match.list[lengths(match.list) > 0]
  431. m0_cols <- unlist(match.list)[grepl("\\.M0", unlist(match.list))]
  432. m1_cols <- unlist(match.list)[grepl("\\.M1", unlist(match.list))]
  433. m2_cols <- unlist(match.list)[grepl("\\.M2", unlist(match.list))]
  434. if (length(m0_cols) > 0 && length(m1_cols) > 0 && length(m2_cols) > 0) {
  435. df.expr$composite.M0 <- Matrix::rowMeans(as.matrix(df.expr[, m0_cols, drop = FALSE]))
  436. df.expr$composite.M1 <- Matrix::rowMeans(as.matrix(df.expr[, m1_cols, drop = FALSE]))
  437. df.expr$composite.M2 <- Matrix::rowMeans(as.matrix(df.expr[, m2_cols, drop = FALSE]))
  438. composite_polarization_cell <- df.expr %>%
  439. dplyr::select(sample_id, expr, composite.M0, composite.M1, composite.M2) %>%
  440. dplyr::mutate(
  441. subtype = c("composite.M0", "composite.M1", "composite.M2")[max.col(as.matrix(dplyr::select(., composite.M0, composite.M1, composite.M2)), ties.method = "first")]
  442. )
  443. composite_polarization_sample <- composite_polarization_cell %>%
  444. dplyr::group_by(sample_id) %>%
  445. dplyr::mutate(expr_scaled = as.numeric(scale(expr))) %>%
  446. dplyr::group_by(sample_id, subtype) %>%
  447. dplyr::summarise(
  448. mean_expression = mean(expr_scaled, na.rm = TRUE),
  449. median_expression = median(expr_scaled, na.rm = TRUE),
  450. n_cells = dplyr::n(),
  451. .groups = "drop"
  452. )
  453. overall_kw <- tryCatch(kruskal.test(mean_expression ~ subtype, data = composite_polarization_sample)$p.value, error = function(e) NA_real_)
  454. pairwise_states <- sort(unique(composite_polarization_sample$subtype))
  455. if (length(pairwise_states) >= 2) {
  456. pairwise_tbl <- utils::combn(pairwise_states, 2, simplify = FALSE) %>%
  457. lapply(function(x) {
  458. current <- composite_polarization_sample %>% dplyr::filter(subtype %in% x)
  459. tibble::tibble(
  460. state_a = x[[1]],
  461. state_b = x[[2]],
  462. t_test_p = tryCatch(t.test(mean_expression ~ subtype, data = current)$p.value, error = function(e) NA_real_),
  463. wilcox_p = tryCatch(wilcox.test(mean_expression ~ subtype, data = current)$p.value, error = function(e) NA_real_)
  464. )
  465. }) %>%
  466. dplyr::bind_rows() %>%
  467. dplyr::mutate(t_test_p_adj_bh = p.adjust(t_test_p, method = "BH"))
  468. }
  469. summary_tbl <- tibble::tibble(
  470. test = c("kruskal_wallis_overall"),
  471. p_value = c(overall_kw)
  472. )
  473. readr::write_tsv(composite_polarization_cell, file.path(stats_dir, "methods_myeloid_polarization_composite_cell_level.tsv"))
  474. readr::write_tsv(composite_polarization_sample, file.path(stats_dir, "methods_myeloid_polarization_composite_sample_level.tsv"))
  475. readr::write_tsv(summary_tbl, file.path(stats_dir, "methods_myeloid_polarization_overall_tests.tsv"))
  476. readr::write_tsv(pairwise_tbl, file.path(stats_dir, "methods_myeloid_polarization_pairwise_tests.tsv"))
  477. }
  478. }
  479. ```
  480. ## 9. Sample-size summary generation for scRNA-seq panels
  481. ```{r sample-size-summary}
  482. sample_size_rows <- list()
  483. if (exists("composite_polarization_sample") && nrow(composite_polarization_sample) > 0) {
  484. sample_size_rows[["ED10D"]] <- composite_polarization_sample %>%
  485. dplyr::mutate(group = gsub("^composite\\.", "", subtype)) %>%
  486. dplyr::group_by(panel = "ED10D", group) %>%
  487. dplyr::summarise(
  488. dataset_object = basename(myeloid_object_path),
  489. biological_unit = "sample",
  490. biological_n = dplyr::n_distinct(sample_id),
  491. supporting_cell_count = sum(n_cells, na.rm = TRUE),
  492. note = "GPNMB expression across composite M0/M1/M2 polarization states",
  493. .groups = "drop"
  494. )
  495. }
  496. atlas_qc_rows <- myeloid_meta %>%
  497. dplyr::group_by(group = etiology_clean) %>%
  498. dplyr::summarise(
  499. dataset_object = basename(myeloid_object_path),
  500. biological_unit = "sample",
  501. biological_n = dplyr::n_distinct(sample_id),
  502. supporting_cell_count = dplyr::n(),
  503. note = "Atlas QC boxplots; attached PDF numbering differs from reviewer email",
  504. .groups = "drop"
  505. )
  506. sample_size_rows[["ED8D"]] <- atlas_qc_rows %>% dplyr::mutate(panel = "ED8D")
  507. sample_size_rows[["ED8E"]] <- atlas_qc_rows %>% dplyr::mutate(panel = "ED8E")
  508. sample_size_summary <- dplyr::bind_rows(sample_size_rows) %>%
  509. dplyr::select(panel, group, dataset_object, biological_unit, biological_n, supporting_cell_count, note)
  510. readr::write_tsv(sample_size_summary, file.path(output_dir, "sample_size_summary.tsv"))
  511. sample_size_summary
  512. ```
  513. ## 10. Session info and object manifest
  514. ```{r session-info}
  515. sessionInfo()
  516. ```

03_gpnmb_scRNA_methods_reproducibility.Rmd at commit f1d7843, no license · at the source

Overview

Authors: Neil Savage1,2, Shan Grewal1,2, Muhammad Vaseem Shaikh2,3, Franz J Zemp4,5, Dillon Mckenna2,3, Nicholas Mikolajewicz6,7, Hinda Najem8, Joanna Pyczek4, Jiuran Wei6,7, Mohamed A B Taleb1,2, Lucas C Asselstine1,2, Alisha Anand1,2, Shawn C Chafe2,3, Kui Zhai2,3, William T Maich1,2, Chirayu R Chokshi1,2, Hardikkumar Patel1,2, Tiegan E Korman1,2, Minomi Subapanditha9, Zoya Tabunshchyk9
and 15 other authorsNazanin Tatari1,2, Petar Miletic2,3, David Chen6,7, Sebastian Pacheco8, Abdelsimar T Omar10, Bill Wang10, Hong Han1,2, Jennifer A Chan4, Kevin R Brown6,7, Chitra Venugopal2,3, Thomas Kislinger11, Amy B Heimberger8, Jason Moffat6,7, Douglas J Mahoney4,12, Sheila K Singh1,2,3,13
13 affiliations
  1. Department of Biochemistry and Biomedical Sciences, McMaster University, Hamilton, Ontario Canada
  2. Centre for Discovery in Cancer Research, McMaster University, Hamilton, Ontario Canada
  3. Department of Surgery, Faculty of Health Sciences, McMaster University, Hamilton, Ontario Canada
  4. Arnie Charbonneau Cancer Institute, Cumming School of Medicine, University of Calgary, Calgary, Alberta Canada
  5. Department of Biochemistry and Molecular Biology, University of Calgary, Calgary, Alberta Canada
  6. Department of Molecular Genetics, University of Toronto, Toronto, Ontario Canada
  7. Program in Genetics and Genome Biology, The Hospital for Sick Children, Toronto, Ontario Canada
  8. Feinberg School of Medicine, Northwestern University, Chicago, IL USA
  9. McMaster Immunology Research Centre, McMaster University, Hamilton, Ontario Canada
  10. Division of Neurosurgery, Department of Surgery, Faculty of Health Sciences, McMaster University, Hamilton, Ontario Canada
  11. Department of Medical Biophysics, University of Toronto, Toronto, Ontario Canada
  12. Department of Microbiology, Immunology and Infectious Disease, University of Calgary, Calgary, Alberta Canada
  13. School of Cancer and Pharmaceutical Sciences, Comprehensive Cancer Centre, King’s College London, London, UK
Institutions: McMaster University (Canada); University of Calgary (Canada); University of Toronto (Canada); Hospital for Sick Children (Canada); Northwestern University (United States); King's College London (United Kingdom)
Journal: Nature, volume 656, issue 8129, pages 1013-1022
Dates: received 9 December 2024; accepted 8 May 2026; published online 1 July 2026; in print 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41586-026-10641-1 · PMID 42386964 · PMCID PMC13518245 · OpenAlex W7166813464
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), mouse (organism), other condition (population), cellular / molecular (subfield)
Methods: Statistics, Preprocessing, Evoked potentials
Keywords: Cancer immunotherapy, Target validation, CNS cancer
MeSH: Brain Neoplasms*, Eye Proteins*, Glioblastoma*, Immunotherapy, Adoptive*, Membrane Glycoproteins*, Myeloid Cells*, Receptors, Chimeric Antigen*, T-Lymphocytes*, Animals, Antigens, Neoplasm, Female, Humans, Male, Mice, Tumor Microenvironment, Xenograft Model Antitumor Assays (* major topic)
Topic: CAR-T cell therapy research (Oncology, Medicine), according to OpenAlex
Citations: cited by 4 papers (Europe PMC); 84 references in the paper

Abstract

Glioblastoma is a lethal brain tumour for which current multimodal treatment rarely prevents recurrence1. Therapeutic failure is driven by extensive intratumoural cellular heterogeneity2 with a microenvironment dominated by tumour-associated macrophages that sustain tumour growth and immunosuppression3. Although chimeric antigen receptor (CAR)-T cell therapies are being developed for glioblastoma, sustained response has been undermined by non-uniform antigen expression, antigen loss and microenvironmental barriers that are not directly engaged by tumour-targeting designs4. These limitations motivate new strategies that address the disease as a coupled tumour–immune system rather than a single malignant compartment. Here we use a multi-omic target discovery platform to identify GPNMB as a dual-compartment antigen in glioblastoma. Anti-GPNMB CAR-T cells showed potent anti-tumour activity, with long-term disease control in orthotopic patient-derived xenografts and syngeneic glioma models through concomitant depletion of GPNMB+ tumour and immunosuppressive myeloid populations. By collapsing tumour control and microenvironmental reprogramming, these findings provide a new strategy for antigen selection and targeting in heterogenous, myeloid-rich solid cancers.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 5 matches between paragraphs and lines of code.

NMikolajewicz/Savage-2026

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: f1d784337ee3eb922be701bc03de2e72dd33ccff, 2 April 2026
Languages: R (4)
Size: 5 files, 4 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, 3 notebooks
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: ggplot2 (4 files), Seurat (4 files), tidyverse (4 files), patchwork (3 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
5 files

Code availability

All analysis code is available at GitHub (https://github.com/NMikolajewicz/Savage-2026).

Reproduced under the paper's license (CC BY), from the paper cited above.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 4 scripts, each with its path and the digest of its content;
  • 5 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Data Availability Statement

Raw data for single-cell RNA experiments have been deposited as follows: primary and recurrent, 10.6084/m9.figshare.25917628.v1 (ref. 68); and Gpnmb data, 10.6084/m9.figshare.27643794.v1 (ref. 69). Raw data for all samples used in mass spectrometry have been deposited to the Mass Spectrometry Interactive Virtual Environment (MassIVE) with ID MSV000087947 (https://massive.ucsd.edu/ProteoSAFe/QueryMSV?id=MSV000087947). NanoString data are also available on the Gene Expression Omnibus under accession code GSE177549 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE177549). Public scRNA-seq data used the following: GSE182109 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE182109) (ref. 18), https://cbl-dev.cells.ucsc.edu (ref. 70), SCR_002001 (https://scicrunch.org/resolver/SCR_002001/)(ref. 71), GSE185386 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE185386) (ref. 72), GSE156793 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE156793) (ref. 67), GSE186538 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE186538) (ref. 73), GSE203552 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE203552) (ref. 74), GSE118257 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE118257) (ref. 75), E-MTAB-8230 (http://www.ebi.ac.uk/arrayexpress/experiments/E-MTAB-8230/) (ref. 76), GSE131907 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE131907) (ref. 77), GSE127774 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE127774) (ref. 78), GSE157827 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE157827) (ref. 79), https://cells.ucsc.edu/?ds=ms# (ref. 80), GSE157783 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE157783) (ref. 81), GSE202371 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE202371) (ref. 82), GSE131928 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE131928) (ref. 83) and GSE165388 (https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE165388) (ref. 84). Source data are provided with this paper.

All analysis code is available at GitHub (https://github.com/NMikolajewicz/Savage-2026).

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 2, 28 September 2026

  • Publisher: n/a → Nature Portfolio

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 35 authors, 3 keywords, 16 MeSH terms, 83 references.

Cite

This paper

Savage, N., Grewal, S., Shaikh, M. V., Zemp, F. J., Mckenna, D., Mikolajewicz, N., Najem, H., Pyczek, J., Wei, J., Taleb, M. A. B., Asselstine, L. C., Anand, A., Chafe, S. C., Zhai, K., Maich, W. T., Chokshi, C. R., Patel, H., Korman, T. E., Subapanditha, M., . . . Singh, S. K. (2026). Dual tumour-myeloid targeting of glioblastoma with GPNMB CAR-T cells. Nature, 656(8129), 1013-1022. https://doi.org/10.1038/s41586-026-10641-1

BibTeX

@article{savage2026dual,
author = {Savage, Neil and Grewal, Shan and Shaikh, Muhammad Vaseem and Zemp, Franz J and Mckenna, Dillon and Mikolajewicz, Nicholas and Najem, Hinda and Pyczek, Joanna and Wei, Jiuran and Taleb, Mohamed A B and Asselstine, Lucas C and Anand, Alisha and Chafe, Shawn C and Zhai, Kui and Maich, William T and Chokshi, Chirayu R and Patel, Hardikkumar and Korman, Tiegan E and Subapanditha, Minomi and Tabunshchyk, Zoya and Tatari, Nazanin and Miletic, Petar and Chen, David and Pacheco, Sebastian and Omar, Abdelsimar T and Wang, Bill and Han, Hong and Chan, Jennifer A and Brown, Kevin R and Venugopal, Chitra and Kislinger, Thomas and Heimberger, Amy B and Moffat, Jason and Mahoney, Douglas J and Singh, Sheila K},
title = {{Dual tumour-myeloid targeting of glioblastoma with GPNMB CAR-T cells}},
journal = {Nature},
year = {2026},
month = jul,
volume = {656},
number = {8129},
pages = {1013--1022},
publisher = {Nature Portfolio},
issn = {0028-0836},
doi = {10.1038/s41586-026-10641-1},
url = {https://doi.org/10.1038/s41586-026-10641-1},
pmid = {42386964},
pmcid = {PMC13518245}
}

RIS

TY - JOUR
AU - Savage, Neil
AU - Grewal, Shan
AU - Shaikh, Muhammad Vaseem
AU - Zemp, Franz J
AU - Mckenna, Dillon
AU - Mikolajewicz, Nicholas
AU - Najem, Hinda
AU - Pyczek, Joanna
AU - Wei, Jiuran
AU - Taleb, Mohamed A B
AU - Asselstine, Lucas C
AU - Anand, Alisha
AU - Chafe, Shawn C
AU - Zhai, Kui
AU - Maich, William T
AU - Chokshi, Chirayu R
AU - Patel, Hardikkumar
AU - Korman, Tiegan E
AU - Subapanditha, Minomi
AU - Tabunshchyk, Zoya
AU - Tatari, Nazanin
AU - Miletic, Petar
AU - Chen, David
AU - Pacheco, Sebastian
AU - Omar, Abdelsimar T
AU - Wang, Bill
AU - Han, Hong
AU - Chan, Jennifer A
AU - Brown, Kevin R
AU - Venugopal, Chitra
AU - Kislinger, Thomas
AU - Heimberger, Amy B
AU - Moffat, Jason
AU - Mahoney, Douglas J
AU - Singh, Sheila K
TI - Dual tumour-myeloid targeting of glioblastoma with GPNMB CAR-T cells
T2 - Nature
J2 - Nature
PY - 2026
DA - 2026/07/01
VL - 656
IS - 8129
SP - 1013
EP - 1022
SN - 0028-0836
PB - Nature Portfolio
DO - 10.1038/s41586-026-10641-1
UR - https://doi.org/10.1038/s41586-026-10641-1
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41586-026-10641-1",
"type": "article-journal",
"title": "Dual tumour-myeloid targeting of glioblastoma with GPNMB CAR-T cells",
"container-title": "Nature",
"author": [
{
"family": "Savage",
"given": "Neil"
},
{
"family": "Grewal",
"given": "Shan"
},
{
"family": "Shaikh",
"given": "Muhammad Vaseem"
},
{
"family": "Zemp",
"given": "Franz J"
},
{
"family": "Mckenna",
"given": "Dillon"
},
{
"family": "Mikolajewicz",
"given": "Nicholas"
},
{
"family": "Najem",
"given": "Hinda"
},
{
"family": "Pyczek",
"given": "Joanna"
},
{
"family": "Wei",
"given": "Jiuran"
},
{
"family": "Taleb",
"given": "Mohamed A B"
},
{
"family": "Asselstine",
"given": "Lucas C"
},
{
"family": "Anand",
"given": "Alisha"
},
{
"family": "Chafe",
"given": "Shawn C"
},
{
"family": "Zhai",
"given": "Kui"
},
{
"family": "Maich",
"given": "William T"
},
{
"family": "Chokshi",
"given": "Chirayu R"
},
{
"family": "Patel",
"given": "Hardikkumar"
},
{
"family": "Korman",
"given": "Tiegan E"
},
{
"family": "Subapanditha",
"given": "Minomi"
},
{
"family": "Tabunshchyk",
"given": "Zoya"
},
{
"family": "Tatari",
"given": "Nazanin"
},
{
"family": "Miletic",
"given": "Petar"
},
{
"family": "Chen",
"given": "David"
},
{
"family": "Pacheco",
"given": "Sebastian"
},
{
"family": "Omar",
"given": "Abdelsimar T"
},
{
"family": "Wang",
"given": "Bill"
},
{
"family": "Han",
"given": "Hong"
},
{
"family": "Chan",
"given": "Jennifer A"
},
{
"family": "Brown",
"given": "Kevin R"
},
{
"family": "Venugopal",
"given": "Chitra"
},
{
"family": "Kislinger",
"given": "Thomas"
},
{
"family": "Heimberger",
"given": "Amy B"
},
{
"family": "Moffat",
"given": "Jason"
},
{
"family": "Mahoney",
"given": "Douglas J"
},
{
"family": "Singh",
"given": "Sheila K"
}
],
"container-title-short": "Nature",
"volume": "656",
"issue": "8129",
"page": "1013-1022",
"DOI": "10.1038/s41586-026-10641-1",
"PMID": "42386964",
"PMCID": "PMC13518245",
"ISSN": "0028-0836",
"publisher": "Nature Portfolio",
"URL": "https://doi.org/10.1038/s41586-026-10641-1",
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
1
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1016/j.cell.2026.05.026 [code]
The critical role of the endogenous immune compartment after CAR T cell therapy in recurrent GBM.
Journal: Cell
In common: Seurat, patchwork, ggplot2, 1 other tool, other condition, 8 references
[2] doi:10.1038/s41586-026-10612-6 [code]
Acquired genetic and cell-state changes in IDH-mutant glioma progression.
Journal: Nature
In common: Seurat, patchwork, ggplot2, 1 other tool, other condition, cellular / molecular, 7 references
[3] doi:10.1016/j.xcrm.2026.102766 [code]
A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.
Journal: Cell reports. Medicine
In common: Seurat, patchwork, ggplot2, 1 other tool, other condition, cellular / molecular, 6 references
[4] doi:10.1093/neuonc/noag119 [code]
Single-nucleus profiling of postmortem diffuse midline gliomas identifies mitochondrial biogenesis as a resistance mechanism to imipridone therapy.
Journal: Neuro-oncology
In common: Seurat, patchwork, ggplot2, 1 other tool, other condition, 4 references
[5] doi:10.1038/s41467-026-74058-0
IQGAP3 bridges matrix stiffness with glioma stem cell maintenance and radioresistance by stabilizing SOX2.
Journal: Nature communications
In common: other condition, mouse, cellular / molecular, 6 references
[6] doi:10.1038/s41593-026-02316-x [code]
Single-cell multi-omic atlas and morphogen screening informs midbrain and hindbrain organoid engineering.
Journal: Nature neuroscience
In common: Seurat, patchwork, ggplot2, 1 other tool, mouse, 3 references
[7] doi:10.1371/journal.pbio.3003757 [code]
Cell type-agnostic transcriptomic signatures enable uniform comparisons of neural maturation.
Journal: PLoS biology
In common: Seurat, ggplot2, tidyverse, mouse, 3 references
[8] doi:10.1038/s41586-026-10310-3 [code]
DNA damage burden causes selective CUX2 neuron loss in neuroinflammation.
Journal: Nature
In common: Seurat, patchwork, ggplot2, 1 other tool, mouse, cellular / molecular, 2 references
[9] doi:10.3390/ijms27104466 [code]
Uncovering the Key Circuit FOSL2/FOS/EGR3/EGR1, Contributing to the Hyperexcitability of Excitatory Neurons in the Epileptic Temporal Cortex and Hippocampus.
Journal: International journal of molecular sciences
In common: Seurat, patchwork, ggplot2, 1 other tool, 2 references
[10] doi:10.1038/s41597-026-07185-4 [code]
A multi-center cross-platform single-cell multimodal atlas of the mouse cerebral cortex.
Journal: Scientific data
In common: Seurat, patchwork, ggplot2, 1 other tool, mouse, 2 references

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.