Chimeric Antigen Receptor T-cell (CAR-T) therapy represents a revolutionary approach to cancer treatment, where a patient’s T-cells are genetically modified to recognize and attack cancer cells. The success of CAR-T therapy critically depends on identifying suitable target antigens—proteins expressed on cancer cells that can serve as targets for the engineered T-cells.
This vignette exemplifies a comprehensive computational workflow for CAR-T target assessment at the isoform level. We demonstrate this workflow using ERBB2 (HER2/neu) as a case study, systematically evaluating isoforms based on subcellular localization, membrane topology, epitope retention, and expression patterns. ERBB” is a receptor tyrosine kinase overexpressed in approximately 25-30% of breast cancers Slamon, D.J. et al. and an established target for monoclonal antibody therapy (trastuzumab) Yoon, J. et al..
Download protein sequences for all HER2 isoforms from Ensembl (GRCh38, release 115). We retrieve sequences for all protein-coding transcripts to ensure comprehensive coverage.
# Connect to Ensembl
ensembl <- useEnsembl(biomart = "genes", dataset = "hsapiens_gene_ensembl")
# Get protein-coding ERBB2 transcripts
get_gene_transcripts <- function(gene_symbol, mart) {
getBM(
attributes = c("ensembl_gene_id", "ensembl_transcript_id",
"transcript_biotype", "transcript_length"),
filters = "hgnc_symbol",
values = gene_symbol,
mart = mart
) %>%
filter(transcript_biotype == "protein_coding") %>%
arrange(desc(transcript_length))
}
her2_transcripts <- get_gene_transcripts("ERBB2", ensembl)
# Retrieve protein sequences
get_protein_sequences <- function(transcript_ids, mart) {
seqs <- getBM(
attributes = c("ensembl_transcript_id", "peptide"),
filters = "ensembl_transcript_id",
values = transcript_ids,
mart = mart
)
sequences <- AAStringSet(seqs$peptide)
names(sequences) <- seqs$ensembl_transcript_id
sequences[width(sequences) > 0] # Remove empty sequences
}
her2_sequences_all <- get_protein_sequences(her2_transcripts$ensembl_transcript_id, ensembl)
print(her2_sequences_all)## AAStringSet object of length 24:
## width seq names
## [1] 102 MKLRLPASPETHLDMLRHLYQGC...NYALAVLDNGDPLNNTTPVTGA ENST00000578709
## [2] 1226 MKLRLPASPETHLDMLRHLYQGC...STFKGTPTAENPEYLGLDVPV* ENST00000584601
## [3] 177 XPCHPECQPQNGSVTCFGPEADQ...KETELRKVKGIWIPDGENVKIP ENST00000582818
## [4] 1056 MELAALCRWGLLLALLPPGAAST...PAPGAGGMVHHRHRSSSTRNM* ENST00000584450
## [5] 604 MKLRLPASPETHLDMLRHLYQGC...IWKFPDEEGACQPCPINCTHS* ENST00000584014
## ... ... ...
## [20] 1267 MELAALCRWGLLLALLPPGAAST...STFKGTPTAENPEYLGLDVPV* ENST00000959774
## [21] 1194 MELAALCRWGLLLALLPPGAAST...STFKGTPTAENPEYLGLDVPV* ENST00000938923
## [22] 1137 MELAALCRWGLLLALLPPGAAST...STFKGTPTAENPEYLGLDVPV* ENST00000938925
## [23] 1290 MELAALCRWGLLLALLPPGAAST...STFKGTPTAENPEYLGLDVPV* ENST00000938924
## [24] 1140 MELAALCRWGLLLALLPPGAAST...STFKGTPTAENPEYLGLDVPV* ENST00000959775
We use pre-processed TCGA/GTEx expression data from Xena Browser. This dataset provides transcript-level expression across cancer and normal tissues. Note: Ensembl’s September 2025 update added ~120,000 new transcripts not yet reflected in available expression databases, so some isoforms are absent from the expression data.
# File paths
expr_file <- "data/TcgaTargetGtex_rsem_isoform_tpm.gz"
filtered_file <- "results/her2_isoforms_filtered.txt"
output_rds <- "results/her2_expr_filtered.rds"
# HER2 transcripts to extract
her2_transcripts <- names(her2_sequences_all)
# Filter expression file line-by-line (efficient for large files)
con <- gzfile(expr_file, "r")
header <- readLines(con, n = 1)
writeLines(header, filtered_file)
line_count <- 0
matched_count <- 0
while (TRUE) {
lines <- readLines(con, n = 1000)
if (length(lines) == 0) break
line_count <- line_count + length(lines)
# Extract HER2 transcript lines
pattern <- paste0("^(", paste(her2_transcripts, collapse = "|"), ")(\\.|\\t)")
matching_lines <- grep(pattern, lines, value = TRUE)
if (length(matching_lines) > 0) {
write(matching_lines, file = filtered_file, append = TRUE)
matched_count <- matched_count + length(matching_lines)
}
}
close(con)
# Read filtered data and save as RDS
her2_expr <- read_tsv(filtered_file, show_col_types = FALSE)
saveRDS(her2_expr, output_rds)Identify which HER2 isoforms have expression data and filter sequences accordingly.
# Load pre-filtered expression data
her2_expr <- readRDS("../../results/HER2_analysis/her2_expr_filtered.rds")
# Extract transcript IDs (remove version numbers)
her2_available_transcripts <- her2_expr %>%
mutate(transcript_clean = str_remove(sample, "\\..*")) %>%
pull(transcript_clean) %>%
unique()
# Filter sequences to those with expression data
sequence_names_clean <- str_remove(names(her2_sequences_all), "\\..*")
keep_indices <- sequence_names_clean %in% her2_available_transcripts
her2_sequences_filtered <- her2_sequences_all[keep_indices]
cat("Sequences with expression data:", length(her2_sequences_filtered), "\n")## Sequences with expression data: 10
Save sequences in FASTA format for external prediction tools (DeepLoc2 for subcellular localization, DeepTMHMM for membrane topology).
fasta_file <- "../../data/her2_isoforms_filtered.fasta"
writeXStringSet(her2_sequences_filtered, filepath = fasta_file)
cat("✓ FASTA saved:", fasta_file, "\n\n")
cat("Upload to:\n")
cat(" • DeepLoc2: https://services.healthtech.dtu.dk/services/DeepLoc-2.1/\n")
cat(" • DeepTMHMM: https://dtu.biolib.com/DeepTMHMM\n")Align sequences using MAFFT with the --localpair
algorithm, which is optimized for sequences with large
insertions/deletions from alternative splicing. This ensures proper
alignment of conserved functional domains.
temp_input <- tempfile(fileext = ".fasta")
temp_output <- tempfile(fileext = ".fasta")
writeXStringSet(her2_sequences_filtered, temp_input)
# MAFFT alignment optimized for isoforms
system2("mafft",
args = c("--localpair", "--maxiterate", "1000", "--quiet", temp_input),
stdout = temp_output)
her2_aligned <- readAAStringSet(temp_output)
unlink(c(temp_input, temp_output))
cat("Alignment width:", unique(width(her2_aligned)), "positions\n")## Alignment width: 1294 positions
Import external prediction results and perform integrated analysis: classify isoforms by subcellular localization, map membrane topology to aligned sequences, and identify therapeutic epitope retention across all isoforms.
# ===== Import DeepLoc2 Results =====
deeploc_results <- read_csv("../../results/HER2_analysis/HER2_deeploc21_filtered.csv",
show_col_types = FALSE)
deeploc_filtered <- deeploc_results %>%
mutate(transcript_clean = str_remove(Protein_ID, "\\..*")) %>%
filter(transcript_clean %in% her2_available_transcripts)
# ===== Import DeepTMHMM Results =====
parse_deeptmhmm <- function(file_path) {
lines <- readLines(file_path)
results <- list()
i <- 1
while (i <= length(lines)) {
if (startsWith(lines[i], ">")) {
transcript_id <- str_extract(lines[i], "(?<=>)[^ ]+")
sequence <- lines[i + 1]
topology <- lines[i + 2]
results[[transcript_id]] <- list(
transcript_id = transcript_id,
sequence = sequence,
topology = topology
)
i <- i + 3
} else {
i <- i + 1
}
}
return(results)
}
deeptmhmm_results <- parse_deeptmhmm(
"../../results/HER2_analysis/HER2_deeptmhmm_predicted_topologies_filtered.3line"
)
# ===== Classify Isoforms by Localization =====
transcript_classification <- deeploc_filtered %>%
dplyr::select(Protein_ID, `Cell membrane`, Extracellular) %>%
mutate(
transcript_clean = str_remove(Protein_ID, "\\..*"),
classification = case_when(
`Cell membrane` > Extracellular ~ "Membrane-bound",
Extracellular > `Cell membrane` ~ "Secreted",
TRUE ~ "Ambiguous"
)
)
classification_colors <- c(
"Membrane-bound" = "#E74C3C",
"Secreted" = "#3498DB",
"Ambiguous" = "#95A5A6"
)
# ===== Map Topology to Alignment =====
map_topology_to_alignment <- function(deeptmhmm_results, aligned_sequences) {
map_dfr(names(deeptmhmm_results), function(tid) {
topology <- deeptmhmm_results[[tid]]$topology
aligned_seq <- as.character(aligned_sequences[tid])
topology_chars <- strsplit(topology, "")[[1]]
aligned_chars <- strsplit(aligned_seq, "")[[1]]
aligned_topology <- character(length(aligned_chars))
# Transfer topology to aligned positions
original_pos <- 1
for (aligned_pos in 1:length(aligned_chars)) {
if (aligned_chars[aligned_pos] == "-") {
aligned_topology[aligned_pos] <- "-"
} else {
if (original_pos <= length(topology_chars)) {
aligned_topology[aligned_pos] <- topology_chars[original_pos]
}
original_pos <- original_pos + 1
}
}
data.frame(
position = 1:length(aligned_chars),
aa = aligned_chars,
topology = aligned_topology,
transcript_id = tid
) %>%
mutate(
topology_type = case_when(
topology == "S" ~ "Signal peptide",
topology == "O" ~ "Extracellular",
topology == "M" ~ "Transmembrane",
topology == "I" ~ "Intracellular",
topology == "-" ~ "Alignment gap",
TRUE ~ "Unknown"
)
)
})
}
aligned_segments <- map_topology_to_alignment(deeptmhmm_results, her2_aligned)
# ===== Map Epitopes Across Isoforms =====
# Therapeutic antibody epitope positions in canonical HER2
epitope_positions <- list(
P = list(c(257, 267), c(308, 318)), # Pertuzumab
T = list(c(580, 582), c(593, 595), c(610, 625)) # Trastuzumab
)
map_epitopes_from_alignment <- function(aligned_seqs, canonical_name, epitope_pos) {
canonical_seq <- as.character(aligned_seqs[[canonical_name]])
epitope_map_list <- list()
for(seq_name in names(aligned_seqs)) {
isoform_seq <- as.character(aligned_seqs[[seq_name]])
canonical_ungapped_pos <- 0
epitope_positions_aligned <- list()
for(i in 1:nchar(canonical_seq)) {
canon_char <- substr(canonical_seq, i, i)
iso_char <- substr(isoform_seq, i, i)
if(canon_char != "-") canonical_ungapped_pos <- canonical_ungapped_pos + 1
# Check if position is in epitope and isoform has residue
for(epitope_name in names(epitope_pos)) {
for(region in epitope_pos[[epitope_name]]) {
if(canonical_ungapped_pos >= region[1] &&
canonical_ungapped_pos <= region[2] &&
iso_char != "-") {
epitope_positions_aligned[[length(epitope_positions_aligned) + 1]] <- data.frame(
epitope = epitope_name,
position = i,
canonical_pos = canonical_ungapped_pos
)
}
}
}
}
if(length(epitope_positions_aligned) > 0) {
epitope_map_list[[seq_name]] <- bind_rows(epitope_positions_aligned)
}
}
bind_rows(epitope_map_list, .id = "transcript_id")
}
epitope_mapping <- map_epitopes_from_alignment(
aligned_seqs = her2_aligned,
canonical_name = "ENST00000269571",
epitope_pos = epitope_positions
)
# Summarize epitope retention
epitope_summary <- epitope_mapping %>%
group_by(transcript_id, epitope) %>%
summarise(n_positions = n(), .groups = "drop") %>%
pivot_wider(names_from = epitope, values_from = n_positions, values_fill = 0)
print(epitope_summary)## # A tibble: 9 × 3
## transcript_id P T
## <chr> <int> <int>
## 1 ENST00000269571 22 22
## 2 ENST00000445658 11 22
## 3 ENST00000578199 22 22
## 4 ENST00000578502 22 22
## 5 ENST00000580074 0 10
## 6 ENST00000582818 0 22
## 7 ENST00000584014 22 22
## 8 ENST00000584450 22 22
## 9 ENST00000584601 22 22
Create comprehensive visualization combining subcellular localization (DeepLoc2), membrane topology (DeepTMHMM), and epitope locations in a single integrated figure.
# Define isoform display order
transcript_order <- c(
"ENST00000580074",
"ENST00000578709",
"ENST00000584601",
"ENST00000584450",
"ENST00000584014",
"ENST00000582818",
"ENST00000578502", # Longest
"ENST00000578199", # Secreted
"ENST00000445658", # Truncated
"ENST00000269571" # Canonical
)
# ===== DeepLoc2 Heatmap =====
localization_cols <- c("Cell membrane", "Cytoplasm", "Endoplasmic reticulum",
"Extracellular", "Golgi apparatus", "Lysosome/Vacuole",
"Mitochondrion", "Nucleus", "Peroxisome", "Plastid")
plot_data_combined <- deeploc_filtered %>%
mutate(Protein_ID = factor(Protein_ID, levels = transcript_order)) %>%
dplyr::select(Protein_ID, all_of(localization_cols)) %>%
pivot_longer(cols = -Protein_ID, names_to = "localization", values_to = "probability")
deeploc_heatmap <- ggplot(plot_data_combined,
aes(x = localization, y = Protein_ID, fill = probability)) +
geom_tile(color = "white", linewidth = 0.5) +
scale_fill_gradientn(
colors = c("#4A90B8", "#5E9DC3", "#FFFACD", "#F26D5E", "#D73027"),
values = c(0, 0.2, 0.5, 0.8, 1),
limits = c(0, 1)
) +
labs(title = "HER2 Isoforms", x = NULL, y = NULL, fill = "Probability") +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1, size = 16),
axis.text.y = element_text(size = 16, family = "mono"),
plot.title = element_text(size = 18, face = "bold"),
legend.text = element_text(size = 10),
legend.title = element_text(size = 16),
legend.position = "top",
panel.grid = element_blank()
)
# ===== Topology Plot with Epitopes =====
plot_topology_with_epitopes <- function(aligned_segments, epitope_data, transcript_order) {
topology_colors <- c(
"Signal peptide" = "#C41C24",
"Extracellular" = "#FFB20F",
"Transmembrane" = "#18848C",
"Intracellular" = "#96BDC6",
"Alignment gap" = "#EDE7E3"
)
epitope_colors <- c("P" = "#55a630", "T" = "#ff5d8f")
aligned_segments <- aligned_segments %>%
mutate(transcript_id = factor(transcript_id, levels = transcript_order))
# Base topology plot
p <- ggplot(aligned_segments, aes(x = position, y = transcript_id, fill = topology_type)) +
geom_tile(height = 0.8, width = 1) +
scale_fill_manual(values = topology_colors, name = "Topology") +
scale_x_continuous(expand = c(0, 0), breaks = seq(0, max(aligned_segments$position), 400)) +
labs(x = "Amino acid position (aligned)", y = NULL) +
theme_minimal() +
theme(
axis.text.y = element_blank(),
axis.text.x = element_text(size = 16),
axis.title.x = element_text(size = 18),
legend.position = "right",
legend.text = element_text(size = 16),
legend.title = element_text(size = 18),
panel.grid = element_blank()
)
# Add epitope overlay
if(!is.null(epitope_data) && nrow(epitope_data) > 0) {
epitope_data <- epitope_data %>%
mutate(transcript_id = factor(transcript_id, levels = transcript_order))
p <- p +
new_scale_fill() +
geom_tile(data = epitope_data,
aes(x = position, y = transcript_id, fill = epitope),
height = 0.8, width = 1, alpha = 0.9, inherit.aes = FALSE) +
scale_fill_manual(
values = epitope_colors,
name = "Epitope",
labels = c("P" = "Pertuzumab", "T" = "Trastuzumab")
)
}
return(p)
}
topology_plot <- plot_topology_with_epitopes(aligned_segments, epitope_mapping, transcript_order)
# ===== Combined Plot =====
combined_plot <- deeploc_heatmap + topology_plot +
plot_layout(widths = c(1, 1.5))
print(combined_plot)Filter to targetable isoforms (membrane-bound with retained epitopes) and merge with tissue annotations from TCGA/GTEx.
# Targetable isoforms based on topology and epitope analysis
targetable_isoforms <- c("ENST00000269571", "ENST00000584601", "ENST00000584450",
"ENST00000584014", "ENST00000578502", "ENST00000578199",
"ENST00000445658", "ENST00000582818")
# Load tissue annotations
phenotype <- read_tsv("../../data/HER2_TCGA_GTEX_category_2026.txt", show_col_types = FALSE) %>%
separate(TCGA_GTEX_main_category,
into = c("dataset", "tissue_or_disease"),
sep = " ", extra = "merge") %>%
mutate(tissue_or_disease = tolower(tissue_or_disease))
# Transform and filter expression data
her2_expr_long <- her2_expr %>%
pivot_longer(cols = -sample, names_to = "sample_id", values_to = "log2_tpm001") %>%
dplyr::rename(transcript_id = sample) %>%
mutate(
transcript_clean = str_remove(transcript_id, "\\..*"),
# Convert log2(TPM + 0.001) to log2(TPM + 1)
tpm = 2^log2_tpm001 - 0.001,
tpm = pmax(tpm, 0),
log2_tpm_plus1 = log2(tpm + 1)
) %>%
filter(transcript_clean %in% targetable_isoforms)
# Merge with phenotype and classification
her2_expr_final <- her2_expr_long %>%
left_join(phenotype, by = c("sample_id" = "sample")) %>%
left_join(transcript_classification, by = "transcript_clean") %>%
filter(!is.na(dataset))Compare expression across normal tissues (GTEx) and cancer types (TCGA) to assess tumor selectivity and on-target/off-tumor toxicity risk.
# Categorize normal tissues by clinical importance
tissue_categories <- list(
"Critical" = c("brain", "blood", "blood vessel", "heart", "liver", "lung", "nerve"),
"Important" = c("pancreas", "kidney", "stomach", "colon", "small intestine", "bladder"),
"Others" = c("spleen", "uterus", "ovary", "testis", "prostate", "breast", "skin")
)
tissue_category_df <- data.frame(
tissue_or_disease = unlist(tissue_categories),
category = rep(names(tissue_categories), sapply(tissue_categories, length))
) %>%
mutate(category = factor(category, levels = c("Critical", "Important", "Others")))
# ===== GTEx Data =====
gtex_data <- her2_expr_final %>%
filter(dataset == "GTEX") %>%
left_join(tissue_category_df, by = "tissue_or_disease") %>%
filter(!is.na(category)) %>%
arrange(category, transcript_clean) %>%
group_by(category) %>%
mutate(position_within_cat = as.numeric(factor(transcript_clean))) %>%
ungroup() %>%
mutate(
gap_offset = case_when(
category == "Critical" ~ 0,
category == "Important" ~ 9,
category == "Others" ~ 18
),
x_position = gap_offset + position_within_cat
)
gtex_data <- gtex_data %>%
mutate(
group_label = paste0(category, "_", transcript_clean),
)
# ===== TCGA Data =====
tcga_cancers <- c("breast invasive carcinoma", "stomach adenocarcinoma", "glioblastoma multiforme")
tcga_data <- her2_expr_final %>%
filter(dataset == "TCGA", tissue_or_disease %in% tcga_cancers) %>%
arrange(tissue_or_disease, transcript_clean) %>%
group_by(tissue_or_disease) %>%
mutate(position_within_disease = as.numeric(factor(transcript_clean))) %>%
ungroup() %>%
mutate(
gap_offset = case_when(
tissue_or_disease == "breast invasive carcinoma" ~ 0,
tissue_or_disease == "stomach adenocarcinoma" ~ 9,
tissue_or_disease == "glioblastoma multiforme" ~ 18
),
x_position = gap_offset + position_within_disease + 27 # Add offset to place after GTEx (26 + 1 gap)
)
tcga_data <- tcga_data %>%
mutate(
group_label = paste0(tissue_or_disease, "_", transcript_clean),
)
# ===== Combined Visualization =====
# Combine datasets
combined_data <- bind_rows(
gtex_data %>%
dplyr::select(transcript_clean, log2_tpm_plus1, classification,
dataset, group_label, x_position),
tcga_data %>%
dplyr::select(transcript_clean, log2_tpm_plus1, classification,
dataset, group_label, x_position)
)
expression_plot <- ggplot(combined_data,
aes(x = x_position,
y = log2_tpm_plus1,
fill = classification,
group = interaction(dataset, group_label))) +
geom_boxplot(outlier.shape = NA) +
scale_fill_manual(values = classification_colors, name = "Localization") +
scale_x_continuous(
breaks = combined_data %>%
distinct(x_position, group_label) %>%
pull(x_position),
labels = combined_data %>%
distinct(x_position, group_label) %>%
pull(group_label)
) +
coord_cartesian(ylim = c(0, 7.5)) +
geom_vline(xintercept = 26.5, linetype = "dashed", color = "gray50", linewidth = 0.5) +
# Add dataset labels at the top
annotate("text", x = 13, y = max(combined_data$log2_tpm_plus1) * 1.05,
label = "GTEx (Normal Tissues)", fontface = "bold", size = 4) +
annotate("text", x = 40, y = max(combined_data$log2_tpm_plus1) * 1.05,
label = "TCGA (Cancer Types)", fontface = "bold", size = 4) +
labs(
title = "HER2 Isoform Expression Across Normal Tissues and Cancer Types",
subtitle = "GTEx tissues grouped by essentiality, followed by TCGA cancer types",
x = "Tissue/Cancer Type_Isoform",
y = "Expression (log2(TPM + 1))"
) +
theme_minimal() +
theme(
plot.margin = margin(t = 20, r = 10, b = 10, l = 40),
axis.text.x = element_text(angle = 45, hjust = 1, size = 7),
axis.text.y = element_text(size = 10),
axis.title = element_text(size = 12),
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 11),
legend.position = "right",
legend.text = element_text(size = 10)
)
print(expression_plot)## R version 4.5.1 (2025-06-13)
## Platform: aarch64-apple-darwin20
## Running under: macOS Tahoe 26.2
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: Europe/Copenhagen
## tzcode source: internal
##
## attached base packages:
## [1] stats4 stats graphics grDevices utils datasets methods
## [8] base
##
## other attached packages:
## [1] lubridate_1.9.4 forcats_1.0.1 stringr_1.6.0
## [4] dplyr_1.1.4 purrr_1.2.0 readr_2.1.6
## [7] tidyr_1.3.2 tibble_3.3.0 ggplot2_4.0.1
## [10] tidyverse_2.0.0 patchwork_1.3.2 ggnewscale_0.5.2
## [13] Biostrings_2.78.0 Seqinfo_1.0.0 XVector_0.50.0
## [16] IRanges_2.44.0 S4Vectors_0.48.0 BiocGenerics_0.56.0
## [19] generics_0.1.4 biomaRt_2.66.0
##
## loaded via a namespace (and not attached):
## [1] KEGGREST_1.50.0 gtable_0.3.6 xfun_0.55
## [4] bslib_0.9.0 httr2_1.2.2 Biobase_2.70.0
## [7] tzdb_0.5.0 vctrs_0.6.5 tools_4.5.1
## [10] parallel_4.5.1 curl_7.0.0 AnnotationDbi_1.72.0
## [13] RSQLite_2.4.5 blob_1.3.0 pkgconfig_2.0.3
## [16] dbplyr_2.5.1 RColorBrewer_1.1-3 S7_0.2.1
## [19] lifecycle_1.0.4 compiler_4.5.1 farver_2.1.2
## [22] progress_1.2.3 htmltools_0.5.9 sass_0.4.10
## [25] yaml_2.3.12 pillar_1.11.1 crayon_1.5.3
## [28] jquerylib_0.1.4 cachem_1.1.0 tidyselect_1.2.1
## [31] digest_0.6.39 stringi_1.8.7 labeling_0.4.3
## [34] fastmap_1.2.0 grid_4.5.1 cli_3.6.5
## [37] magrittr_2.0.4 utf8_1.2.6 withr_3.0.2
## [40] prettyunits_1.2.0 filelock_1.0.3 scales_1.4.0
## [43] rappdirs_0.3.3 bit64_4.6.0-1 timechange_0.3.0
## [46] rmarkdown_2.30 httr_1.4.7 bit_4.6.0
## [49] otel_0.2.0 png_0.1-8 hms_1.1.4
## [52] memoise_2.0.1 evaluate_1.0.5 knitr_1.51
## [55] BiocFileCache_3.0.0 rlang_1.1.6 glue_1.8.0
## [58] DBI_1.2.3 xml2_1.5.1 vroom_1.6.7
## [61] rstudioapi_0.17.1 jsonlite_2.0.0 R6_2.6.1