Load libraries
Load Annotated Healthy
Integrated Object
DefaultAssay(reference_integrated) <- "RNA"
reference_integrated
An object of class Seurat
57379 features across 11482 samples within 7 assays
Active assay: RNA (36601 features, 2902 variable features)
3 layers present: scale.data, data, counts
6 other assays present: ADT, prediction.score.celltype.l1, prediction.score.celltype.l2, prediction.score.celltype.l3, SCT, integrated
2 dimensional reductions calculated: pca, umap
Clustree for
Resolution Selection (Recommended)
ElbowPlot(reference_integrated, ndims = 50)

# ✅ Clustree — visualize cluster stability across resolutions
library(clustree)
clustree(reference_integrated, prefix = "integrated_snn_res.") +
ggtitle("Cluster stability across resolutions") +
theme(legend.position = "right")

Silhouette Score
(Quantitative Optimum)
library(cluster)
# Extract PCA embeddings used for clustering
pca_embeddings <- Embeddings(reference_integrated, "pca")[, 1:20]
dist_matrix <- dist(pca_embeddings)
res_cols <- grep("integrated_snn_res",
colnames(reference_integrated@meta.data), value = TRUE)
sil_scores <- sapply(res_cols, function(res) {
clusters <- as.integer(reference_integrated@meta.data[[res]])
# Need at least 2 clusters
if (length(unique(clusters)) < 2) return(NA)
sil <- silhouette(clusters, dist_matrix)
mean(sil[, 3]) # mean silhouette width
})
# Print ranked results
sil_df <- data.frame(
resolution = res_cols,
n_clusters = sapply(res_cols, function(r)
length(unique(reference_integrated@meta.data[[r]]))),
mean_silhouette = round(sil_scores, 4)
)
print(sil_df[order(-sil_df$mean_silhouette), ])
resolution n_clusters mean_silhouette
integrated_snn_res.0.2 integrated_snn_res.0.2 5 0.1862
integrated_snn_res.0.3 integrated_snn_res.0.3 7 0.1791
integrated_snn_res.0.1 integrated_snn_res.0.1 2 0.1775
integrated_snn_res.0.5 integrated_snn_res.0.5 10 0.0770
integrated_snn_res.1 integrated_snn_res.1 16 0.0721
integrated_snn_res.0.4 integrated_snn_res.0.4 9 0.0718
integrated_snn_res.0.8 integrated_snn_res.0.8 13 0.0690
# Plot
ggplot(sil_df, aes(x = resolution, y = mean_silhouette)) +
geom_col(fill = "steelblue") +
geom_text(aes(label = n_clusters), vjust = -0.5, size = 3) +
theme_classic() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
labs(title = "Mean Silhouette Score per Resolution",
subtitle = "Numbers = cluster count | Higher = better separation",
x = "Resolution", y = "Mean Silhouette Width")

Azimuth label
concordance
concordance <- sapply(res_cols, function(res) {
tbl <- table(reference_integrated@meta.data[[res]],
reference_integrated$predicted.celltype.l2)
purity <- apply(tbl, 1, function(x) max(x) / sum(x))
mean(purity)
})
conc_df <- data.frame(
resolution = gsub("integrated_snn_res.", "res.", res_cols),
n_clusters = sapply(res_cols, function(r)
length(unique(reference_integrated@meta.data[[r]]))),
az_purity = round(concordance, 4)
)
cat("Azimuth concordance ranked:\n")
Azimuth concordance ranked:
print(conc_df[order(-conc_df$az_purity), ])
resolution n_clusters az_purity
integrated_snn_res.0.5 res.0.5 10 0.8391
integrated_snn_res.1 res.1 16 0.8329
integrated_snn_res.0.8 res.0.8 13 0.8304
integrated_snn_res.0.4 res.0.4 9 0.8206
integrated_snn_res.0.3 res.0.3 7 0.8200
integrated_snn_res.0.2 res.0.2 5 0.8044
integrated_snn_res.0.1 res.0.1 2 0.7913
ggplot(conc_df, aes(x = resolution, y = az_purity)) +
geom_col(fill = "#2196F3") +
geom_text(aes(label = n_clusters), vjust = -0.5, size = 3.5) +
ylim(0, 1) +
theme_classic() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
labs(title = "Azimuth Label Purity per Resolution",
subtitle = "Numbers = cluster count | Higher = cleaner biological mapping",
x = "Resolution", y = "Mean Cluster Purity")

DimPlot grid — visual
comparison
res_cols <- res_cols[as.numeric(gsub("integrated_snn_res\\.", "", res_cols)) >= 0.1 &
as.numeric(gsub("integrated_snn_res\\.", "", res_cols)) <= 1]
plots <- lapply(res_cols, function(res) {
n <- length(unique(reference_integrated@meta.data[[res]]))
DimPlot(reference_integrated, group.by = res,
label = TRUE, label.size = 2.5, pt.size = 0.1) +
ggtitle(paste0("res=", gsub("integrated_snn_res.", "", res),
" (", n, " clusters)")) +
NoLegend() +
theme(plot.title = element_text(size = 9))
})
wrap_plots(plots, ncol = 3) +
plot_annotation(title = "UMAP across all tested resolutions")

SET FINAL
RESOLUTION
best_res <- "integrated_snn_res.0.3"
Idents(reference_integrated) <- best_res
reference_integrated$seurat_clusters <- reference_integrated@meta.data[[best_res]]
cat("✅ Final resolution: res.0.3\n")
✅ Final resolution: res.0.3
cat("Clusters:", length(unique(reference_integrated$seurat_clusters)), "\n")
Clusters: 7
print(table(reference_integrated$seurat_clusters))
0 1 2 3 4 5 6
5485 3998 522 491 412 341 233
Validation plots:
dataset mixing and initial labels
# Validation plots
p1 <- DimPlot(reference_integrated, group.by = "predicted.celltype.l2",
label = TRUE, repel = TRUE, label.size = 3) + NoLegend()
p2 <- DimPlot(reference_integrated, group.by = "dataset")
p3 <- DimPlot(reference_integrated, group.by = "seurat_clusters", label = TRUE,label.box = T, repel = TRUE)
(p1 | p2) / p3

DimPlot(reference_integrated, group.by = "seurat_clusters", label = TRUE,label.box = T, repel = TRUE)

Check FOXP3 Check
FOXP3 in Cluster 5
# Check FOXP3 in both locations of cluster 6
FeaturePlot(reference_integrated,
features = c("FOXP3", "IL2RA", "CTLA4", "TIGIT"),
split.by = "seurat_clusters", ncol = 4) +
plot_annotation(title = "Treg markers — both locations")

treg_cells <- subset(reference_integrated, subset = seurat_clusters == "5")
VlnPlot(treg_cells,
features = c("FOXP3", "IL2RA", "CTLA4", "HLA-DRB1"),
group.by = "seurat_clusters", pt.size = 0)

Validation — Confirm
Both Are Treg
# ── Fix: use RNA assay normalized expression ────────────────────────────
DefaultAssay(reference_integrated) <- "RNA"
# Get CTLA4 normalized expression (data layer)
ctla4_expr <- GetAssayData(reference_integrated,
assay = "RNA",
layer = "data")["CTLA4", ]
# Split cluster 6 by CTLA4 high/low
reference_integrated$treg_subset <- ifelse(
reference_integrated$seurat_clusters == "6",
ifelse(ctla4_expr > 0, "Treg_CTLA4+", "Treg_CTLA4-"),
"non-Treg"
)
table(reference_integrated$treg_subset)
non-Treg Treg_CTLA4- Treg_CTLA4+
11249 222 11
reference_integrated$treg_subset <- ifelse(
reference_integrated$seurat_clusters == "6",
ifelse(reference_integrated$Treg1 > 0.2, "Treg activated", "Treg naive"),
"non-Treg"
)
# Or check if TIGIT exists too
if ("TIGIT" %in% rownames(reference_integrated)) {
tigit_expr <- GetAssayData(reference_integrated, assay = "RNA", layer = "data")["TIGIT", ]
reference_integrated$treg_subset <- ifelse(
reference_integrated$seurat_clusters == "6",
ifelse(ctla4_expr > 0 & tigit_expr > 0, "Treg effector", "Treg resting"),
"non-Treg"
)
}
DimPlot(reference_integrated, group.by = "treg_subset", label = TRUE) +
ggtitle("Cluster 6 Treg heterogeneity by CTLA4/TIGIT")

NA
NA
Azimuth Label
Validation
DimPlot(reference_integrated, group.by = "predicted.celltype.l1",
label = TRUE, repel = TRUE, label.size = 3) + NoLegend()

DimPlot(reference_integrated, group.by = "predicted.celltype.l2",
label = TRUE, repel = TRUE, label.size = 3) + NoLegend()

DimPlot(reference_integrated, group.by = "predicted.celltype.l3",
label = TRUE, repel = TRUE, label.size = 3) + NoLegend()

DimPlot(reference_integrated, group.by = "singler.hpca",
label = TRUE, repel = TRUE, label.size = 3) + NoLegend()

DimPlot(reference_integrated, group.by = "singler.immune",
label = TRUE, repel = TRUE, label.size = 3) + NoLegend()

print(table(reference_integrated$predicted.celltype.l1, reference_integrated$seurat_clusters))
0 1 2 3 4 5 6
CD4 T 5485 3998 522 487 412 340 232
CD8 T 0 0 0 4 0 1 1
print(table(reference_integrated$predicted.celltype.l2, reference_integrated$seurat_clusters))
0 1 2 3 4 5 6
CD4 CTL 0 0 0 11 0 0 0
CD4 Naive 2030 7 1 0 20 1 5
CD4 Proliferating 5 0 0 0 0 7 0
CD4 TCM 3421 3972 519 346 391 154 222
CD4 TEM 0 8 1 134 0 1 0
Treg 29 11 1 0 1 178 6
print(table(reference_integrated$predicted.celltype.l3, reference_integrated$seurat_clusters))
0 1 2 3 4 5 6
CD4 CTL 0 0 0 11 0 0 0
CD4 Naive 2036 9 1 0 20 1 5
CD4 Proliferating 5 0 0 0 0 7 0
CD4 TCM_1 3408 2545 506 74 371 48 201
CD4 TCM_2 1 314 3 26 17 102 13
CD4 TCM_3 8 1104 10 256 3 2 5
CD4 TEM_1 0 1 0 41 0 0 0
CD4 TEM_2 0 9 0 30 0 1 0
CD4 TEM_3 0 2 1 53 0 0 0
Treg Memory 9 14 1 0 1 158 9
Treg Naive 18 0 0 0 0 22 0
print(table(reference_integrated$singler.hpca, reference_integrated$seurat_clusters))
0 1 2 3 4 5 6
B_cell:Memory 1 0 0 0 0 0 0
Neutrophil:uropathogenic_E._coli_UTI89 0 0 0 0 1 0 0
NK_cell:CD56hiCD62L+ 0 0 0 1 0 0 0
T_cell:CD4+ 0 0 1 1 0 0 0
T_cell:CD4+_central_memory 460 2911 316 175 58 238 79
T_cell:CD4+_effector_memory 5 478 11 278 2 23 5
T_cell:CD4+_Naive 4996 578 186 5 315 75 134
T_cell:CD8+ 0 2 0 12 0 1 0
T_cell:CD8+_Central_memory 0 0 0 2 0 0 0
T_cell:CD8+_effector_memory 3 12 7 4 22 0 12
T_cell:CD8+_effector_memory_RA 0 0 0 1 0 0 1
T_cell:CD8+_naive 0 1 0 5 7 0 0
T_cell:gamma-delta 2 0 0 0 0 1 0
T_cell:Treg:Naive 2 1 0 0 0 2 1
print(table(reference_integrated$singler.immune, reference_integrated$seurat_clusters))
0 1 2 3 4 5 6
NK cells 0 1 0 10 1 0 1
T cells, CD4+, memory TREG 6 152 18 3 3 186 5
T cells, CD4+, naive 3827 66 48 0 121 2 100
T cells, CD4+, naive TREG 247 29 1 0 3 108 6
T cells, CD4+, naive, stimulated 22 16 5 0 85 0 7
T cells, CD4+, TFH 1181 1642 165 46 113 23 47
T cells, CD4+, Th1 18 174 18 284 5 11 5
T cells, CD4+, Th1_17 3 240 2 141 0 0 8
T cells, CD4+, Th17 11 992 82 3 10 7 16
T cells, CD4+, Th2 51 675 173 1 23 3 34
T cells, CD8+, naive 107 5 5 2 7 1 4
T cells, CD8+, naive, stimulated 5 3 5 1 39 0 0
FindAllMarkers + Filter
+ Save All Files
# ── Required libraries for FindAllMarkers + filtering + saving ──────────
library(Seurat) # FindAllMarkers, DoHeatmap, DimPlot
library(dplyr) # filter, group_by, arrange, slice_head, pull
library(ggplot2) # plotting
library(RColorBrewer) # heatmap color palettes
library(openxlsx) # write Excel workbook (multi-sheet .xlsx)
DefaultAssay(reference_integrated) <- "RNA"
reference_integrated <- JoinLayers(reference_integrated)
cat("✅ RNA layers joined\n")
✅ RNA layers joined
# Verify layers are now accessible
cat("RNA assay layers:\n")
RNA assay layers:
print(names(Layers(reference_integrated, assay = "RNA")))
NULL
# ── Now re-run FindAllMarkers ──────────────────────────────────────────
DefaultAssay(reference_integrated) <- "RNA"
Idents(reference_integrated) <- "seurat_clusters"
# ── FindAllMarkers on the CORRECT object ───────────────────────────────
ref_markers <- FindAllMarkers(
reference_integrated, # ← was All_samples_Merged (wrong object)
only.pos = TRUE,
min.pct = 0.25,
logfc.threshold = 0.25,
min.pct.diff = 0.20,
verbose = FALSE
)
cat("Total markers before filtering:", nrow(ref_markers), "\n")
Total markers before filtering: 5277
print(head(colnames(ref_markers))) # confirm 'gene' column exists
[1] "p_val" "avg_log2FC" "pct.1" "pct.2" "p_val_adj" "cluster"
# ── Fix: ensure gene is a proper column (Seurat sometimes stores as rownames)
if (!"gene" %in% colnames(ref_markers)) {
ref_markers$gene <- rownames(ref_markers)
cat("✅ gene column added from rownames\n")
}
# ── Blacklist ───────────────────────────────────────────────────────────
blacklist_patterns <- c(
"^TRAV", "^TRBV", "^TRGV", "^TRDV", "^TRBC", "^TRAC", "^TRDC", "^TRGC",
"^IGH", "^IGK", "^IGL", "^IGJ",
"^RPL", "^RPS",
"^MT-",
"^HBA", "^HBB", "^HB[ABZ]",
"^NEAT1$", "^MALAT1$",
"^XIST$"
)
blacklist_regex <- paste(blacklist_patterns, collapse = "|")
# Preview removed genes
to_remove <- ref_markers %>%
dplyr::filter(grepl(blacklist_regex, gene, ignore.case = TRUE))
message("Rows to remove: ", nrow(to_remove))
print(head(to_remove$gene))
[1] "RPS10" "NEAT1" "RPS6KA3" "RPS6KA5" "NEAT1" "RPS6KA3"
# Apply filter
ref_markers_filtered <- ref_markers %>%
dplyr::filter(!grepl(blacklist_regex, gene, ignore.case = TRUE))
cat("Markers after blacklist filter:", nrow(ref_markers_filtered), "\n")
Markers after blacklist filter: 5249
# ── Significance filter ─────────────────────────────────────────────────
ref_markers_sig <- ref_markers_filtered %>%
dplyr::filter(p_val_adj < 0.05)
cat("Significant markers (p_val_adj < 0.05):", nrow(ref_markers_sig), "\n")
Significant markers (p_val_adj < 0.05): 3643
# ── Top 25 per cluster sorted by avg_log2FC ─────────────────────────────
top25_markers <- ref_markers_sig %>%
dplyr::group_by(cluster) %>%
dplyr::arrange(dplyr::desc(avg_log2FC)) %>%
dplyr::slice_head(n = 25) %>%
dplyr::ungroup()
# ── Top 5 per cluster sorted by avg_log2FC ──────────────────────────────
top5_markers <- ref_markers_sig %>%
dplyr::group_by(cluster) %>%
dplyr::arrange(dplyr::desc(avg_log2FC)) %>%
dplyr::slice_head(n = 5) %>%
dplyr::ungroup()
cat("Top25 rows:", nrow(top25_markers), "\n")
Top25 rows: 175
cat("Top5 rows:", nrow(top5_markers), "\n")
Top5 rows: 35
print(top5_markers[, c("cluster","gene","avg_log2FC","pct.1","pct.2","p_val_adj")])
# A tibble: 35 × 6
cluster gene avg_log2FC pct.1 pct.2 p_val_adj
<fct> <chr> <dbl> <dbl> <dbl> <dbl>
1 0 AIF1 2.35 0.261 0.056 3.01e-202
2 0 TMIGD2 2.21 0.316 0.074 2.39e-239
3 0 ACTN1 1.54 0.441 0.169 4.15e-238
4 0 ADTRP 1.47 0.359 0.138 4.24e-168
5 0 LRRN3 1.37 0.262 0.097 1.05e-110
6 1 KLRB1 2.30 0.323 0.062 3.45e-288
7 1 AC006369.1 1.96 0.251 0.062 3.13e-175
8 1 MYO1F 1.94 0.296 0.072 6.41e-212
9 1 ANXA2 1.89 0.436 0.112 0
10 1 LGALS1 1.74 0.322 0.106 7.67e-181
# ℹ 25 more rows
# ℹ Use `print(n = ...)` to see more rows
# ── Save files ──────────────────────────────────────────────────────────
write.csv(ref_markers_filtered,
"CD4_reference_markers_all_filtered.csv", row.names = FALSE)
write.csv(top25_markers,
"CD4_reference_markers_top25_per_cluster.csv", row.names = FALSE)
write.csv(top5_markers,
"CD4_reference_markers_top5_per_cluster.csv", row.names = FALSE)
# Optional Excel workbook
library(openxlsx)
wb <- createWorkbook()
addWorksheet(wb, "All_Filtered")
addWorksheet(wb, "Top25_per_cluster")
addWorksheet(wb, "Top5_per_cluster")
writeData(wb, "All_Filtered", ref_markers_filtered)
writeData(wb, "Top25_per_cluster", top25_markers)
writeData(wb, "Top5_per_cluster", top5_markers)
saveWorkbook(wb, "CD4_reference_markers_summary.xlsx", overwrite = TRUE)
cat("✅ All marker files saved\n")
✅ All marker files saved
Add the filtered marker
results directly to your Seurat object
# ── After your existing FindAllMarkers + filtering code ─────────────────
# Add ALL filtered significant markers as metadata (1 row per cell)
# Create a cluster-to-markers mapping
marker_list_all <- ref_markers_sig %>%
group_by(cluster) %>%
arrange(desc(avg_log2FC)) %>%
slice_head(n = Inf) %>% # all sig markers
summarise(markers_all = paste(gene, collapse = "; "), .groups = "drop")
# Map to all cells
reference_integrated$markers_sig_all <- marker_list_all$markers_all[
match(reference_integrated$seurat_clusters, marker_list_all$cluster)
]
# ── TOP 25 per cluster ──────────────────────────────────────────────────
marker_list_top25 <- top25_markers %>%
group_by(cluster) %>%
arrange(desc(avg_log2FC)) %>%
slice_head(n = 25) %>%
summarise(markers_top25 = paste(gene, collapse = "; "), .groups = "drop")
reference_integrated$markers_top25 <- marker_list_top25$markers_top25[
match(reference_integrated$seurat_clusters, marker_list_top25$cluster)
]
# ── TOP 5 per cluster ───────────────────────────────────────────────────
marker_list_top5 <- top5_markers %>%
group_by(cluster) %>%
arrange(desc(avg_log2FC)) %>%
slice_head(n = 5) %>%
summarise(markers_top5 = paste(gene, collapse = "; "), .groups = "drop")
reference_integrated$markers_top5 <- marker_list_top5$markers_top5[
match(reference_integrated$seurat_clusters, marker_list_top5$cluster)
]
# ── Store full data.frames as object lists (for easy retrieval) ──────────
reference_integrated@misc$markers_all_filtered <- ref_markers_filtered
reference_integrated@misc$markers_top25 <- top25_markers
reference_integrated@misc$markers_top5 <- top5_markers
cat("✅ Marker lists added to object metadata:\n")
✅ Marker lists added to object metadata:
cat("- markers_sig_all (all sig markers per cluster)\n")
- markers_sig_all (all sig markers per cluster)
cat("- markers_top25 (top 25 per cluster)\n")
- markers_top25 (top 25 per cluster)
cat("- markers_top5 (top 5 per cluster)\n")
- markers_top5 (top 5 per cluster)
cat("- @misc$markers_* data.frames\n")
- @misc$markers_* data.frames
# ── Quick verification ──────────────────────────────────────────────────
head(reference_integrated@meta.data[, grepl("markers", colnames(reference_integrated@meta.data))])
table(nchar(reference_integrated$markers_top5) > 0) # should all be TRUE
TRUE
11482
Quick Visual Summary of
Top Markers(Heatmap)
# DoHeatmap of top 5 per cluster for quick biological validation
library(RColorBrewer)
top5_genes <- top5_markers %>%
group_by(cluster) %>%
slice_head(n = 5) %>%
pull(gene) %>%
unique()
DefaultAssay(reference_integrated) <- "RNA"
DoHeatmap(
reference_integrated,
features = top5_genes,
group.by = "seurat_clusters",
label = TRUE,
raster = FALSE
) +
scale_fill_gradientn(colors = rev(brewer.pal(11, "RdBu"))) +
ggtitle("Top 5 markers per cluster (res.0.3) — pre-annotation validation")

Quick Visual Summary of
Top Markers(Dotplot)
# ── SCpubr DotPlot for top 5 markers ───────────────────────────────────
DefaultAssay(reference_integrated) <- "RNA"
# Get top 5 genes per cluster (already computed)
top5_genes <- top5_markers %>%
dplyr::group_by(cluster) %>%
dplyr::slice_head(n = 5) %>%
dplyr::pull(gene) %>%
unique()
# ── SCpubr v1.x — minimal working syntax ───────────────────────────────
SCpubr::do_DotPlot(sample = reference_integrated,
features = top5_genes,
flip = TRUE,
)

Quick Visual Summary of
Top Markers(Dotplot)
# Check cluster 0 immediately
DefaultAssay(reference_integrated) <- "RNA"
# Myeloid vs T cell markers
VlnPlot(reference_integrated,
features = c("AIF1", "CD3D", "CD4", "CD14", "CD68", "LYZ"),
group.by = "seurat_clusters", pt.size = 0)

# Azimuth predicted labels in cluster 0
table(reference_integrated$predicted.celltype.l2[
reference_integrated$seurat_clusters == "0"])
CD4 Naive CD4 Proliferating CD4 TCM Treg
2030 5 3421 29
FeaturePlot to check
TEMRA
FeaturePlot(
object = reference_integrated,
features = c("PRF1", "NKG7", "FGFBP2", "GNLY"),
cols = c("lightgrey", "red"),label = T,
ncol = 2
)

R Annotation Code
# # COMPLETE CELL TYPE ANNOTATION CODE
#
# # Step 1: Ensure clusters are characters
# reference_integrated$seurat_clusters <- as.character(reference_integrated$seurat_clusters)
#
# # Step 2: Define named labels (exactly matches your 0-6 clusters)
# cluster_labels <- c(
# "0" = "CD4 Tnaive (CCR7+SELL+TCF7+)", # Canonical naive
# "1" = "CD4 TCM (CD161+/IL7R+)", # TCM, KLRB1/CD161+ Th17-memory
# "2" = "CD4 TCM (CCR4+/Th2-like)", # Activated TCM, CCR4+BATF+
# "3" = "CD4 CTL/Temra (GZMK+GZMA+CCL5+)", # Cytotoxic effector, Temra
# "4" = "CD4 TEM (NF-kB activated)", # Effector memory, stress-activated
# "5" = "CD4 Treg (FOXP3+Helios+CD25+)", # Natural tTreg, highest confidence
# "6" = "CD4 Tnaive-RTE (IGF1R+)" # Quiescent/RTE naive subset (Quiescent/RTE naive subset)
# )
#
# # Step 3: Create vector (you already verified this works)
# clusters_chr <- as.character(reference_integrated$seurat_clusters)
# cell_type_vec <- cluster_labels[clusters_chr]
#
# # Step 4: SAFE ASSIGNMENT (direct metadata write)
# reference_integrated@meta.data$cell_type <- cell_type_vec
#
# # Step 5: Force factor ordering 0→6 for plots
# reference_integrated@meta.data$cell_type <- factor(
# reference_integrated@meta.data$cell_type,
# levels = c("CD4 Tnaive (CCR7+SELL+TCF7+)",
# "CD4 TCM (CD161+/IL7R+)",
# "CD4 TCM (CCR4+/Th2-like)",
# "CD4 CTL/Temra (GZMK+GZMA+CCL5+)",
# "CD4 TEM (NF-kB activated)",
# "CD4 Treg (FOXP3+Helios+CD25+)",
# "CD4 Tnaive-RTE (IGF1R+)")
# )
# Step 6: VALIDATE
cat("✅ Assignment complete!\n")
✅ Assignment complete!
print(table(reference_integrated$seurat_clusters, reference_integrated@meta.data$cell_type))
CD4 Tnaive (CCR7+SELL+TCF7+) CD4 TCM (CD161+/IL7R+) CD4 TCM (CCR4+/Th2-like) CD4 CTL/Temra (GZMK+GZMA+CCL5+)
0 5485 0 0 0
1 0 3998 0 0
2 0 0 522 0
3 0 0 0 491
4 0 0 0 0
5 0 0 0 0
6 0 0 0 0
CD4 TEM (NF-kB activated) CD4 Treg (FOXP3+Helios+CD25+) CD4 Tnaive-RTE (IGF1R+)
0 0 0 0
1 0 0 0
2 0 0 0
3 0 0 0
4 412 0 0
5 0 341 0
6 0 0 233
print(table(reference_integrated@meta.data$cell_type))
CD4 Tnaive (CCR7+SELL+TCF7+) CD4 TCM (CD161+/IL7R+) CD4 TCM (CCR4+/Th2-like)
5485 3998 522
CD4 CTL/Temra (GZMK+GZMA+CCL5+) CD4 TEM (NF-kB activated) CD4 Treg (FOXP3+Helios+CD25+)
491 412 341
CD4 Tnaive-RTE (IGF1R+)
233
# Step 7: VISUALIZE
p1 <- DimPlot(reference_integrated, group.by = "seurat_clusters",
label = TRUE, label.size = 3) + ggtitle("Clusters 0-6")
p2 <- DimPlot(reference_integrated, group.by = "cell_type",
label = TRUE, repel = TRUE, label.size = 3) + ggtitle("Cell Types")
p1 | p2

# Step 8: Cross-check with Azimuth
table(reference_integrated$predicted.celltype.l2,
reference_integrated@meta.data$cell_type)
CD4 Tnaive (CCR7+SELL+TCF7+) CD4 TCM (CD161+/IL7R+) CD4 TCM (CCR4+/Th2-like)
CD4 CTL 0 0 0
CD4 Naive 2030 7 1
CD4 Proliferating 5 0 0
CD4 TCM 3421 3972 519
CD4 TEM 0 8 1
Treg 29 11 1
CD4 CTL/Temra (GZMK+GZMA+CCL5+) CD4 TEM (NF-kB activated) CD4 Treg (FOXP3+Helios+CD25+)
CD4 CTL 11 0 0
CD4 Naive 0 20 1
CD4 Proliferating 0 0 7
CD4 TCM 346 391 154
CD4 TEM 134 0 1
Treg 0 1 178
CD4 Tnaive-RTE (IGF1R+)
CD4 CTL 0
CD4 Naive 5
CD4 Proliferating 0
CD4 TCM 222
CD4 TEM 0
Treg 6
Save annotated
reference
# Save annotated reference
saveRDS(reference_integrated, "CD4_reference_annotated_with_markers.rds")
cat("✅ Annotated reference with markers saved!\n")
✅ Annotated reference with markers saved!
LS0tCnRpdGxlOiAiUmVtb3ZlIGNsdXN0ZXIgMTIgY2VsbHMg4oCUIENsZWFuIFJlLUludGVncmF0aW9uIgphdXRob3I6IE5hc2lyIE1haG1vb2QgQWJiYXNpCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IHRydWUKICAgIHRoZW1lOiBqb3VybmFsCi0tLQoKCiMgTG9hZCBsaWJyYXJpZXMKYGBge3IsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDgpCmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KG1vbm9jbGUzKQpsaWJyYXJ5KFNldXJhdFdyYXBwZXJzKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKb3B0aW9ucyhmdXR1cmUuZ2xvYmFscy5tYXhTaXplID0gOGU5KQpzZXQuc2VlZCgxMjMpCgojIOKUgOKUgCBEZWZpbmUgaXNfaGkoKSBPTkNFIGhlcmUgc28gaXQgaXMgYXZhaWxhYmxlIGluIEFMTCBkb3duc3RyZWFtIGNodW5rcyDilIDilIAKaXNfaGkgPC0gZnVuY3Rpb24oZywgem1hdCwgeiA9IDAuNSkgewogIGlmICghZyAlaW4lIHJvd25hbWVzKHptYXQpKSByZXR1cm4ocmVwKEZBTFNFLCBuY29sKHptYXQpKSkKICB6bWF0W2csIF0gPiB6Cn0KYGBgCgojIExvYWQgQW5ub3RhdGVkIEhlYWx0aHkgSW50ZWdyYXRlZCBPYmplY3QKYGBge3IgbG9hZCBvYmplY3R9CnJlZmVyZW5jZV9pbnRlZ3JhdGVkPC0gcmVhZFJEUygiQ0Q0X3JlZmVyZW5jZV9hbm5vdGF0ZWQucmRzIikKCkRlZmF1bHRBc3NheShyZWZlcmVuY2VfaW50ZWdyYXRlZCkgPC0gIlJOQSIKCnJlZmVyZW5jZV9pbnRlZ3JhdGVkCmBgYAoKCiMjIENsdXN0cmVlIGZvciBSZXNvbHV0aW9uIFNlbGVjdGlvbiAoUmVjb21tZW5kZWQpCmBgYHtyICwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KRWxib3dQbG90KHJlZmVyZW5jZV9pbnRlZ3JhdGVkLCBuZGltcyA9IDUwKQoKIyDinIUgQ2x1c3RyZWUg4oCUIHZpc3VhbGl6ZSBjbHVzdGVyIHN0YWJpbGl0eSBhY3Jvc3MgcmVzb2x1dGlvbnMKbGlicmFyeShjbHVzdHJlZSkKCmNsdXN0cmVlKHJlZmVyZW5jZV9pbnRlZ3JhdGVkLCBwcmVmaXggPSAiaW50ZWdyYXRlZF9zbm5fcmVzLiIpICsKICBnZ3RpdGxlKCJDbHVzdGVyIHN0YWJpbGl0eSBhY3Jvc3MgcmVzb2x1dGlvbnMiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikKCmBgYAoKIyMgU2lsaG91ZXR0ZSBTY29yZSAoUXVhbnRpdGF0aXZlIE9wdGltdW0pCmBgYHtyICwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KbGlicmFyeShjbHVzdGVyKQoKIyBFeHRyYWN0IFBDQSBlbWJlZGRpbmdzIHVzZWQgZm9yIGNsdXN0ZXJpbmcKcGNhX2VtYmVkZGluZ3MgPC0gRW1iZWRkaW5ncyhyZWZlcmVuY2VfaW50ZWdyYXRlZCwgInBjYSIpWywgMToyMF0KZGlzdF9tYXRyaXggICAgPC0gZGlzdChwY2FfZW1iZWRkaW5ncykKCnJlc19jb2xzIDwtIGdyZXAoImludGVncmF0ZWRfc25uX3JlcyIsIAogICAgICAgICAgICAgICAgICBjb2xuYW1lcyhyZWZlcmVuY2VfaW50ZWdyYXRlZEBtZXRhLmRhdGEpLCB2YWx1ZSA9IFRSVUUpCgpzaWxfc2NvcmVzIDwtIHNhcHBseShyZXNfY29scywgZnVuY3Rpb24ocmVzKSB7CiAgY2x1c3RlcnMgPC0gYXMuaW50ZWdlcihyZWZlcmVuY2VfaW50ZWdyYXRlZEBtZXRhLmRhdGFbW3Jlc11dKQogICMgTmVlZCBhdCBsZWFzdCAyIGNsdXN0ZXJzCiAgaWYgKGxlbmd0aCh1bmlxdWUoY2x1c3RlcnMpKSA8IDIpIHJldHVybihOQSkKICBzaWwgPC0gc2lsaG91ZXR0ZShjbHVzdGVycywgZGlzdF9tYXRyaXgpCiAgbWVhbihzaWxbLCAzXSkgICMgbWVhbiBzaWxob3VldHRlIHdpZHRoCn0pCgojIFByaW50IHJhbmtlZCByZXN1bHRzCnNpbF9kZiA8LSBkYXRhLmZyYW1lKAogIHJlc29sdXRpb24gICAgICA9IHJlc19jb2xzLAogIG5fY2x1c3RlcnMgICAgICA9IHNhcHBseShyZXNfY29scywgZnVuY3Rpb24ocikgCiAgICAgICAgICAgICAgICAgICAgIGxlbmd0aCh1bmlxdWUocmVmZXJlbmNlX2ludGVncmF0ZWRAbWV0YS5kYXRhW1tyXV0pKSksCiAgbWVhbl9zaWxob3VldHRlID0gcm91bmQoc2lsX3Njb3JlcywgNCkKKQpwcmludChzaWxfZGZbb3JkZXIoLXNpbF9kZiRtZWFuX3NpbGhvdWV0dGUpLCBdKQoKIyBQbG90CmdncGxvdChzaWxfZGYsIGFlcyh4ID0gcmVzb2x1dGlvbiwgeSA9IG1lYW5fc2lsaG91ZXR0ZSkpICsKICBnZW9tX2NvbChmaWxsID0gInN0ZWVsYmx1ZSIpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbl9jbHVzdGVycyksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsKICBsYWJzKHRpdGxlID0gIk1lYW4gU2lsaG91ZXR0ZSBTY29yZSBwZXIgUmVzb2x1dGlvbiIsCiAgICAgICBzdWJ0aXRsZSA9ICJOdW1iZXJzID0gY2x1c3RlciBjb3VudCB8IEhpZ2hlciA9IGJldHRlciBzZXBhcmF0aW9uIiwKICAgICAgIHggPSAiUmVzb2x1dGlvbiIsIHkgPSAiTWVhbiBTaWxob3VldHRlIFdpZHRoIikKCmBgYAoKCiMjIEF6aW11dGggbGFiZWwgY29uY29yZGFuY2UKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTZ9CmNvbmNvcmRhbmNlIDwtIHNhcHBseShyZXNfY29scywgZnVuY3Rpb24ocmVzKSB7CiAgdGJsICAgIDwtIHRhYmxlKHJlZmVyZW5jZV9pbnRlZ3JhdGVkQG1ldGEuZGF0YVtbcmVzXV0sCiAgICAgICAgICAgICAgICAgICByZWZlcmVuY2VfaW50ZWdyYXRlZCRwcmVkaWN0ZWQuY2VsbHR5cGUubDIpCiAgcHVyaXR5IDwtIGFwcGx5KHRibCwgMSwgZnVuY3Rpb24oeCkgbWF4KHgpIC8gc3VtKHgpKQogIG1lYW4ocHVyaXR5KQp9KQoKY29uY19kZiA8LSBkYXRhLmZyYW1lKAogIHJlc29sdXRpb24gPSBnc3ViKCJpbnRlZ3JhdGVkX3Nubl9yZXMuIiwgInJlcy4iLCByZXNfY29scyksCiAgbl9jbHVzdGVycyA9IHNhcHBseShyZXNfY29scywgZnVuY3Rpb24ocikKICAgICAgICAgICAgICAgIGxlbmd0aCh1bmlxdWUocmVmZXJlbmNlX2ludGVncmF0ZWRAbWV0YS5kYXRhW1tyXV0pKSksCiAgYXpfcHVyaXR5ICA9IHJvdW5kKGNvbmNvcmRhbmNlLCA0KQopCmNhdCgiQXppbXV0aCBjb25jb3JkYW5jZSByYW5rZWQ6XG4iKQpwcmludChjb25jX2RmW29yZGVyKC1jb25jX2RmJGF6X3B1cml0eSksIF0pCgpnZ3Bsb3QoY29uY19kZiwgYWVzKHggPSByZXNvbHV0aW9uLCB5ID0gYXpfcHVyaXR5KSkgKwogIGdlb21fY29sKGZpbGwgPSAiIzIxOTZGMyIpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbl9jbHVzdGVycyksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogIHlsaW0oMCwgMSkgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKwogIGxhYnModGl0bGUgPSAiQXppbXV0aCBMYWJlbCBQdXJpdHkgcGVyIFJlc29sdXRpb24iLAogICAgICAgc3VidGl0bGUgPSAiTnVtYmVycyA9IGNsdXN0ZXIgY291bnQgfCBIaWdoZXIgPSBjbGVhbmVyIGJpb2xvZ2ljYWwgbWFwcGluZyIsCiAgICAgICB4ID0gIlJlc29sdXRpb24iLCB5ID0gIk1lYW4gQ2x1c3RlciBQdXJpdHkiKQpgYGAKCiMjIERpbVBsb3QgZ3JpZCDigJQgdmlzdWFsIGNvbXBhcmlzb24KYGBge3IgLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9MTB9CnJlc19jb2xzIDwtIHJlc19jb2xzW2FzLm51bWVyaWMoZ3N1YigiaW50ZWdyYXRlZF9zbm5fcmVzXFwuIiwgIiIsIHJlc19jb2xzKSkgPj0gMC4xICYgCiAgICAgICAgICAgICAgICAgICAgIGFzLm51bWVyaWMoZ3N1YigiaW50ZWdyYXRlZF9zbm5fcmVzXFwuIiwgIiIsIHJlc19jb2xzKSkgPD0gMV0KCnBsb3RzIDwtIGxhcHBseShyZXNfY29scywgZnVuY3Rpb24ocmVzKSB7CiAgbiA8LSBsZW5ndGgodW5pcXVlKHJlZmVyZW5jZV9pbnRlZ3JhdGVkQG1ldGEuZGF0YVtbcmVzXV0pKQogIERpbVBsb3QocmVmZXJlbmNlX2ludGVncmF0ZWQsIGdyb3VwLmJ5ID0gcmVzLAogICAgICAgICAgbGFiZWwgPSBUUlVFLCBsYWJlbC5zaXplID0gMi41LCBwdC5zaXplID0gMC4xKSArCiAgZ2d0aXRsZShwYXN0ZTAoInJlcz0iLCBnc3ViKCJpbnRlZ3JhdGVkX3Nubl9yZXMuIiwgIiIsIHJlcyksCiAgICAgICAgICAgICAgICAgIiAoIiwgbiwgIiBjbHVzdGVycykiKSkgKwogIE5vTGVnZW5kKCkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDkpKQp9KQp3cmFwX3Bsb3RzKHBsb3RzLCBuY29sID0gMykgKwpwbG90X2Fubm90YXRpb24odGl0bGUgPSAiVU1BUCBhY3Jvc3MgYWxsIHRlc3RlZCByZXNvbHV0aW9ucyIpCmBgYAoKCiMjIFNFVCBGSU5BTCBSRVNPTFVUSU9OCmBgYHtyICwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTEwfQpiZXN0X3JlcyA8LSAiaW50ZWdyYXRlZF9zbm5fcmVzLjAuMyIKCklkZW50cyhyZWZlcmVuY2VfaW50ZWdyYXRlZCkgICAgICAgICA8LSBiZXN0X3JlcwpyZWZlcmVuY2VfaW50ZWdyYXRlZCRzZXVyYXRfY2x1c3RlcnMgPC0gcmVmZXJlbmNlX2ludGVncmF0ZWRAbWV0YS5kYXRhW1tiZXN0X3Jlc11dCgpjYXQoIuKchSBGaW5hbCByZXNvbHV0aW9uOiByZXMuMC4zXG4iKQpjYXQoIkNsdXN0ZXJzOiIsIGxlbmd0aCh1bmlxdWUocmVmZXJlbmNlX2ludGVncmF0ZWQkc2V1cmF0X2NsdXN0ZXJzKSksICJcbiIpCnByaW50KHRhYmxlKHJlZmVyZW5jZV9pbnRlZ3JhdGVkJHNldXJhdF9jbHVzdGVycykpCmBgYAoKIyMgIFZhbGlkYXRpb24gcGxvdHM6IGRhdGFzZXQgbWl4aW5nIGFuZCBpbml0aWFsIGxhYmVscwpgYGB7ciB2YWxpZGF0ZSwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTEwfQojIFZhbGlkYXRpb24gcGxvdHMKcDEgPC0gRGltUGxvdChyZWZlcmVuY2VfaW50ZWdyYXRlZCwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmNlbGx0eXBlLmwyIiwKICAgICAgICAgICAgICBsYWJlbCA9IFRSVUUsIHJlcGVsID0gVFJVRSwgbGFiZWwuc2l6ZSA9IDMpICsgTm9MZWdlbmQoKQpwMiA8LSBEaW1QbG90KHJlZmVyZW5jZV9pbnRlZ3JhdGVkLCBncm91cC5ieSA9ICJkYXRhc2V0IikKcDMgPC0gRGltUGxvdChyZWZlcmVuY2VfaW50ZWdyYXRlZCwgZ3JvdXAuYnkgPSAic2V1cmF0X2NsdXN0ZXJzIiwgbGFiZWwgPSBUUlVFLGxhYmVsLmJveCA9IFQsIHJlcGVsID0gVFJVRSkgCihwMSB8IHAyKSAvIHAzCgpEaW1QbG90KHJlZmVyZW5jZV9pbnRlZ3JhdGVkLCBncm91cC5ieSA9ICJzZXVyYXRfY2x1c3RlcnMiLCBsYWJlbCA9IFRSVUUsbGFiZWwuYm94ID0gVCwgcmVwZWwgPSBUUlVFKSAKCmBgYAoKIyMgQ2hlY2sgRk9YUDMgQ2hlY2sgRk9YUDMgaW4gQ2x1c3RlciA1CmBgYHtyICwgZmlnLndpZHRoPTI4LCBmaWcuaGVpZ2h0PTE2fQojIENoZWNrIEZPWFAzIGluIGJvdGggbG9jYXRpb25zIG9mIGNsdXN0ZXIgNgpGZWF0dXJlUGxvdChyZWZlcmVuY2VfaW50ZWdyYXRlZCwgCiAgICAgICAgICAgIGZlYXR1cmVzID0gYygiRk9YUDMiLCAiSUwyUkEiLCAiQ1RMQTQiLCAiVElHSVQiKSwKICAgICAgICAgICAgc3BsaXQuYnkgPSAic2V1cmF0X2NsdXN0ZXJzIiwgbmNvbCA9IDQpICsKICBwbG90X2Fubm90YXRpb24odGl0bGUgPSAiVHJlZyBtYXJrZXJzIOKAlCBib3RoIGxvY2F0aW9ucyIpCgp0cmVnX2NlbGxzIDwtIHN1YnNldChyZWZlcmVuY2VfaW50ZWdyYXRlZCwgc3Vic2V0ID0gc2V1cmF0X2NsdXN0ZXJzID09ICI1IikKClZsblBsb3QodHJlZ19jZWxscywKZmVhdHVyZXMgPSBjKCJGT1hQMyIsICJJTDJSQSIsICJDVExBNCIsICJITEEtRFJCMSIpLApncm91cC5ieSA9ICJzZXVyYXRfY2x1c3RlcnMiLCBwdC5zaXplID0gMCkKCmBgYAoKIyMgVmFsaWRhdGlvbiDigJQgQ29uZmlybSBCb3RoIEFyZSBUcmVnCmBgYHtyIHRyZWctdmFsaWRhdGlvbiwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KIyDilIDilIAgRml4OiB1c2UgUk5BIGFzc2F5IG5vcm1hbGl6ZWQgZXhwcmVzc2lvbiDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKRGVmYXVsdEFzc2F5KHJlZmVyZW5jZV9pbnRlZ3JhdGVkKSA8LSAiUk5BIgoKIyBHZXQgQ1RMQTQgbm9ybWFsaXplZCBleHByZXNzaW9uIChkYXRhIGxheWVyKQpjdGxhNF9leHByIDwtIEdldEFzc2F5RGF0YShyZWZlcmVuY2VfaW50ZWdyYXRlZCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzc2F5ID0gIlJOQSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBsYXllciA9ICJkYXRhIilbIkNUTEE0IiwgXQoKIyBTcGxpdCBjbHVzdGVyIDYgYnkgQ1RMQTQgaGlnaC9sb3cKcmVmZXJlbmNlX2ludGVncmF0ZWQkdHJlZ19zdWJzZXQgPC0gaWZlbHNlKAogIHJlZmVyZW5jZV9pbnRlZ3JhdGVkJHNldXJhdF9jbHVzdGVycyA9PSAiNiIsCiAgaWZlbHNlKGN0bGE0X2V4cHIgPiAwLCAiVHJlZ19DVExBNCsiLCAiVHJlZ19DVExBNC0iKSwKICAibm9uLVRyZWciCikKCnRhYmxlKHJlZmVyZW5jZV9pbnRlZ3JhdGVkJHRyZWdfc3Vic2V0KQoKCnJlZmVyZW5jZV9pbnRlZ3JhdGVkJHRyZWdfc3Vic2V0IDwtIGlmZWxzZSgKICByZWZlcmVuY2VfaW50ZWdyYXRlZCRzZXVyYXRfY2x1c3RlcnMgPT0gIjYiLAogIGlmZWxzZShyZWZlcmVuY2VfaW50ZWdyYXRlZCRUcmVnMSA+IDAuMiwgIlRyZWcgYWN0aXZhdGVkIiwgIlRyZWcgbmFpdmUiKSwKICAibm9uLVRyZWciCikKCiMgT3IgY2hlY2sgaWYgVElHSVQgZXhpc3RzIHRvbwppZiAoIlRJR0lUIiAlaW4lIHJvd25hbWVzKHJlZmVyZW5jZV9pbnRlZ3JhdGVkKSkgewogIHRpZ2l0X2V4cHIgPC0gR2V0QXNzYXlEYXRhKHJlZmVyZW5jZV9pbnRlZ3JhdGVkLCBhc3NheSA9ICJSTkEiLCBsYXllciA9ICJkYXRhIilbIlRJR0lUIiwgXQogIHJlZmVyZW5jZV9pbnRlZ3JhdGVkJHRyZWdfc3Vic2V0IDwtIGlmZWxzZSgKICAgIHJlZmVyZW5jZV9pbnRlZ3JhdGVkJHNldXJhdF9jbHVzdGVycyA9PSAiNiIsCiAgICBpZmVsc2UoY3RsYTRfZXhwciA+IDAgJiB0aWdpdF9leHByID4gMCwgIlRyZWcgZWZmZWN0b3IiLCAiVHJlZyByZXN0aW5nIiksCiAgICAibm9uLVRyZWciCiAgKQp9CgpEaW1QbG90KHJlZmVyZW5jZV9pbnRlZ3JhdGVkLCBncm91cC5ieSA9ICJ0cmVnX3N1YnNldCIsIGxhYmVsID0gVFJVRSkgKwogIGdndGl0bGUoIkNsdXN0ZXIgNiBUcmVnIGhldGVyb2dlbmVpdHkgYnkgQ1RMQTQvVElHSVQiKQoKCmBgYAojIyBBemltdXRoIExhYmVsIFZhbGlkYXRpb24KYGBge3IgLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQpEaW1QbG90KHJlZmVyZW5jZV9pbnRlZ3JhdGVkLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuY2VsbHR5cGUubDEiLAogICAgICAgIGxhYmVsID0gVFJVRSwgcmVwZWwgPSBUUlVFLCBsYWJlbC5zaXplID0gMykgKyBOb0xlZ2VuZCgpCkRpbVBsb3QocmVmZXJlbmNlX2ludGVncmF0ZWQsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5jZWxsdHlwZS5sMiIsCiAgICAgICAgbGFiZWwgPSBUUlVFLCByZXBlbCA9IFRSVUUsIGxhYmVsLnNpemUgPSAzKSArIE5vTGVnZW5kKCkKRGltUGxvdChyZWZlcmVuY2VfaW50ZWdyYXRlZCwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmNlbGx0eXBlLmwzIiwKICAgICAgICBsYWJlbCA9IFRSVUUsIHJlcGVsID0gVFJVRSwgbGFiZWwuc2l6ZSA9IDMpICsgTm9MZWdlbmQoKQoKRGltUGxvdChyZWZlcmVuY2VfaW50ZWdyYXRlZCwgZ3JvdXAuYnkgPSAic2luZ2xlci5ocGNhIiwKICAgICAgICBsYWJlbCA9IFRSVUUsIHJlcGVsID0gVFJVRSwgbGFiZWwuc2l6ZSA9IDMpICsgTm9MZWdlbmQoKQoKRGltUGxvdChyZWZlcmVuY2VfaW50ZWdyYXRlZCwgZ3JvdXAuYnkgPSAic2luZ2xlci5pbW11bmUiLAogICAgICAgIGxhYmVsID0gVFJVRSwgcmVwZWwgPSBUUlVFLCBsYWJlbC5zaXplID0gMykgKyBOb0xlZ2VuZCgpCgpwcmludCh0YWJsZShyZWZlcmVuY2VfaW50ZWdyYXRlZCRwcmVkaWN0ZWQuY2VsbHR5cGUubDEsIHJlZmVyZW5jZV9pbnRlZ3JhdGVkJHNldXJhdF9jbHVzdGVycykpCnByaW50KHRhYmxlKHJlZmVyZW5jZV9pbnRlZ3JhdGVkJHByZWRpY3RlZC5jZWxsdHlwZS5sMiwgcmVmZXJlbmNlX2ludGVncmF0ZWQkc2V1cmF0X2NsdXN0ZXJzKSkKcHJpbnQodGFibGUocmVmZXJlbmNlX2ludGVncmF0ZWQkcHJlZGljdGVkLmNlbGx0eXBlLmwzLCByZWZlcmVuY2VfaW50ZWdyYXRlZCRzZXVyYXRfY2x1c3RlcnMpKQoKcHJpbnQodGFibGUocmVmZXJlbmNlX2ludGVncmF0ZWQkc2luZ2xlci5ocGNhLCByZWZlcmVuY2VfaW50ZWdyYXRlZCRzZXVyYXRfY2x1c3RlcnMpKQpwcmludCh0YWJsZShyZWZlcmVuY2VfaW50ZWdyYXRlZCRzaW5nbGVyLmltbXVuZSwgcmVmZXJlbmNlX2ludGVncmF0ZWQkc2V1cmF0X2NsdXN0ZXJzKSkKYGBgCgoKIyBGaW5kQWxsTWFya2VycyArIEZpbHRlciArIFNhdmUgQWxsIEZpbGVzCmBgYHtyIH0KIyDilIDilIAgUmVxdWlyZWQgbGlicmFyaWVzIGZvciBGaW5kQWxsTWFya2VycyArIGZpbHRlcmluZyArIHNhdmluZyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKbGlicmFyeShTZXVyYXQpICAgICAgICMgRmluZEFsbE1hcmtlcnMsIERvSGVhdG1hcCwgRGltUGxvdApsaWJyYXJ5KGRwbHlyKSAgICAgICAgIyBmaWx0ZXIsIGdyb3VwX2J5LCBhcnJhbmdlLCBzbGljZV9oZWFkLCBwdWxsCmxpYnJhcnkoZ2dwbG90MikgICAgICAjIHBsb3R0aW5nCmxpYnJhcnkoUkNvbG9yQnJld2VyKSAjIGhlYXRtYXAgY29sb3IgcGFsZXR0ZXMKbGlicmFyeShvcGVueGxzeCkgICAgICMgd3JpdGUgRXhjZWwgd29ya2Jvb2sgKG11bHRpLXNoZWV0IC54bHN4KQoKRGVmYXVsdEFzc2F5KHJlZmVyZW5jZV9pbnRlZ3JhdGVkKSA8LSAiUk5BIgpyZWZlcmVuY2VfaW50ZWdyYXRlZCA8LSBKb2luTGF5ZXJzKHJlZmVyZW5jZV9pbnRlZ3JhdGVkKQpjYXQoIuKchSBSTkEgbGF5ZXJzIGpvaW5lZFxuIikKCiMgVmVyaWZ5IGxheWVycyBhcmUgbm93IGFjY2Vzc2libGUKY2F0KCJSTkEgYXNzYXkgbGF5ZXJzOlxuIikKcHJpbnQobmFtZXMoTGF5ZXJzKHJlZmVyZW5jZV9pbnRlZ3JhdGVkLCBhc3NheSA9ICJSTkEiKSkpCgojIOKUgOKUgCBOb3cgcmUtcnVuIEZpbmRBbGxNYXJrZXJzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgApEZWZhdWx0QXNzYXkocmVmZXJlbmNlX2ludGVncmF0ZWQpIDwtICJSTkEiCklkZW50cyhyZWZlcmVuY2VfaW50ZWdyYXRlZCkgPC0gInNldXJhdF9jbHVzdGVycyIKCiMg4pSA4pSAIEZpbmRBbGxNYXJrZXJzIG9uIHRoZSBDT1JSRUNUIG9iamVjdCDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKcmVmX21hcmtlcnMgPC0gRmluZEFsbE1hcmtlcnMoCiAgcmVmZXJlbmNlX2ludGVncmF0ZWQsICAgICAgICAgICMg4oaQIHdhcyBBbGxfc2FtcGxlc19NZXJnZWQgKHdyb25nIG9iamVjdCkKICBvbmx5LnBvcyAgICAgICAgPSBUUlVFLAogIG1pbi5wY3QgICAgICAgICA9IDAuMjUsCiAgbG9nZmMudGhyZXNob2xkID0gMC4yNSwKICBtaW4ucGN0LmRpZmYgICAgPSAwLjIwLAogIHZlcmJvc2UgICAgICAgICA9IEZBTFNFCikKCmNhdCgiVG90YWwgbWFya2VycyBiZWZvcmUgZmlsdGVyaW5nOiIsIG5yb3cocmVmX21hcmtlcnMpLCAiXG4iKQpwcmludChoZWFkKGNvbG5hbWVzKHJlZl9tYXJrZXJzKSkpICAjIGNvbmZpcm0gJ2dlbmUnIGNvbHVtbiBleGlzdHMKCiMg4pSA4pSAIEZpeDogZW5zdXJlIGdlbmUgaXMgYSBwcm9wZXIgY29sdW1uIChTZXVyYXQgc29tZXRpbWVzIHN0b3JlcyBhcyByb3duYW1lcykKaWYgKCEiZ2VuZSIgJWluJSBjb2xuYW1lcyhyZWZfbWFya2VycykpIHsKICByZWZfbWFya2VycyRnZW5lIDwtIHJvd25hbWVzKHJlZl9tYXJrZXJzKQogIGNhdCgi4pyFIGdlbmUgY29sdW1uIGFkZGVkIGZyb20gcm93bmFtZXNcbiIpCn0KCiMg4pSA4pSAIEJsYWNrbGlzdCDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKYmxhY2tsaXN0X3BhdHRlcm5zIDwtIGMoCiAgIl5UUkFWIiwgIl5UUkJWIiwgIl5UUkdWIiwgIl5UUkRWIiwgIl5UUkJDIiwgIl5UUkFDIiwgIl5UUkRDIiwgIl5UUkdDIiwKICAiXklHSCIsICAiXklHSyIsICAiXklHTCIsICAiXklHSiIsCiAgIl5SUEwiLCAgIl5SUFMiLAogICJeTVQtIiwKICAiXkhCQSIsICAiXkhCQiIsICAiXkhCW0FCWl0iLAogICJeTkVBVDEkIiwgIl5NQUxBVDEkIiwKICAiXlhJU1QkIgopCmJsYWNrbGlzdF9yZWdleCA8LSBwYXN0ZShibGFja2xpc3RfcGF0dGVybnMsIGNvbGxhcHNlID0gInwiKQoKIyBQcmV2aWV3IHJlbW92ZWQgZ2VuZXMKdG9fcmVtb3ZlIDwtIHJlZl9tYXJrZXJzICU+JQogIGRwbHlyOjpmaWx0ZXIoZ3JlcGwoYmxhY2tsaXN0X3JlZ2V4LCBnZW5lLCBpZ25vcmUuY2FzZSA9IFRSVUUpKQptZXNzYWdlKCJSb3dzIHRvIHJlbW92ZTogIiwgbnJvdyh0b19yZW1vdmUpKQpwcmludChoZWFkKHRvX3JlbW92ZSRnZW5lKSkKCiMgQXBwbHkgZmlsdGVyCnJlZl9tYXJrZXJzX2ZpbHRlcmVkIDwtIHJlZl9tYXJrZXJzICU+JQogIGRwbHlyOjpmaWx0ZXIoIWdyZXBsKGJsYWNrbGlzdF9yZWdleCwgZ2VuZSwgaWdub3JlLmNhc2UgPSBUUlVFKSkKY2F0KCJNYXJrZXJzIGFmdGVyIGJsYWNrbGlzdCBmaWx0ZXI6IiwgbnJvdyhyZWZfbWFya2Vyc19maWx0ZXJlZCksICJcbiIpCgojIOKUgOKUgCBTaWduaWZpY2FuY2UgZmlsdGVyIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgApyZWZfbWFya2Vyc19zaWcgPC0gcmVmX21hcmtlcnNfZmlsdGVyZWQgJT4lCiAgZHBseXI6OmZpbHRlcihwX3ZhbF9hZGogPCAwLjA1KQpjYXQoIlNpZ25pZmljYW50IG1hcmtlcnMgKHBfdmFsX2FkaiA8IDAuMDUpOiIsIG5yb3cocmVmX21hcmtlcnNfc2lnKSwgIlxuIikKCiMg4pSA4pSAIFRvcCAyNSBwZXIgY2x1c3RlciBzb3J0ZWQgYnkgYXZnX2xvZzJGQyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKdG9wMjVfbWFya2VycyA8LSByZWZfbWFya2Vyc19zaWcgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGNsdXN0ZXIpICU+JQogIGRwbHlyOjphcnJhbmdlKGRwbHlyOjpkZXNjKGF2Z19sb2cyRkMpKSAlPiUKICBkcGx5cjo6c2xpY2VfaGVhZChuID0gMjUpICU+JQogIGRwbHlyOjp1bmdyb3VwKCkKCiMg4pSA4pSAIFRvcCA1IHBlciBjbHVzdGVyIHNvcnRlZCBieSBhdmdfbG9nMkZDIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAp0b3A1X21hcmtlcnMgPC0gcmVmX21hcmtlcnNfc2lnICU+JQogIGRwbHlyOjpncm91cF9ieShjbHVzdGVyKSAlPiUKICBkcGx5cjo6YXJyYW5nZShkcGx5cjo6ZGVzYyhhdmdfbG9nMkZDKSkgJT4lCiAgZHBseXI6OnNsaWNlX2hlYWQobiA9IDUpICU+JQogIGRwbHlyOjp1bmdyb3VwKCkKCmNhdCgiVG9wMjUgcm93czoiLCBucm93KHRvcDI1X21hcmtlcnMpLCAiXG4iKQpjYXQoIlRvcDUgcm93czoiLCAgbnJvdyh0b3A1X21hcmtlcnMpLCAgIlxuIikKcHJpbnQodG9wNV9tYXJrZXJzWywgYygiY2x1c3RlciIsImdlbmUiLCJhdmdfbG9nMkZDIiwicGN0LjEiLCJwY3QuMiIsInBfdmFsX2FkaiIpXSkKCiMg4pSA4pSAIFNhdmUgZmlsZXMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACndyaXRlLmNzdihyZWZfbWFya2Vyc19maWx0ZXJlZCwKICAgICAgICAgICJDRDRfcmVmZXJlbmNlX21hcmtlcnNfYWxsX2ZpbHRlcmVkLmNzdiIsICAgICByb3cubmFtZXMgPSBGQUxTRSkKd3JpdGUuY3N2KHRvcDI1X21hcmtlcnMsCiAgICAgICAgICAiQ0Q0X3JlZmVyZW5jZV9tYXJrZXJzX3RvcDI1X3Blcl9jbHVzdGVyLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQp3cml0ZS5jc3YodG9wNV9tYXJrZXJzLAogICAgICAgICAgIkNENF9yZWZlcmVuY2VfbWFya2Vyc190b3A1X3Blcl9jbHVzdGVyLmNzdiIsICByb3cubmFtZXMgPSBGQUxTRSkKCiMgT3B0aW9uYWwgRXhjZWwgd29ya2Jvb2sKbGlicmFyeShvcGVueGxzeCkKd2IgPC0gY3JlYXRlV29ya2Jvb2soKQphZGRXb3Jrc2hlZXQod2IsICJBbGxfRmlsdGVyZWQiKQphZGRXb3Jrc2hlZXQod2IsICJUb3AyNV9wZXJfY2x1c3RlciIpCmFkZFdvcmtzaGVldCh3YiwgIlRvcDVfcGVyX2NsdXN0ZXIiKQp3cml0ZURhdGEod2IsICJBbGxfRmlsdGVyZWQiLCAgICAgIHJlZl9tYXJrZXJzX2ZpbHRlcmVkKQp3cml0ZURhdGEod2IsICJUb3AyNV9wZXJfY2x1c3RlciIsIHRvcDI1X21hcmtlcnMpCndyaXRlRGF0YSh3YiwgIlRvcDVfcGVyX2NsdXN0ZXIiLCAgdG9wNV9tYXJrZXJzKQpzYXZlV29ya2Jvb2sod2IsICJDRDRfcmVmZXJlbmNlX21hcmtlcnNfc3VtbWFyeS54bHN4Iiwgb3ZlcndyaXRlID0gVFJVRSkKCmNhdCgi4pyFIEFsbCBtYXJrZXIgZmlsZXMgc2F2ZWRcbiIpCgpgYGAKIyBBZGQgdGhlIGZpbHRlcmVkIG1hcmtlciByZXN1bHRzIGRpcmVjdGx5IHRvIHlvdXIgU2V1cmF0IG9iamVjdCAKYGBge3IgfQojIOKUgOKUgCBBZnRlciB5b3VyIGV4aXN0aW5nIEZpbmRBbGxNYXJrZXJzICsgZmlsdGVyaW5nIGNvZGUg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgojIEFkZCBBTEwgZmlsdGVyZWQgc2lnbmlmaWNhbnQgbWFya2VycyBhcyBtZXRhZGF0YSAoMSByb3cgcGVyIGNlbGwpCiMgQ3JlYXRlIGEgY2x1c3Rlci10by1tYXJrZXJzIG1hcHBpbmcKbWFya2VyX2xpc3RfYWxsIDwtIHJlZl9tYXJrZXJzX3NpZyAlPiUKICBncm91cF9ieShjbHVzdGVyKSAlPiUKICBhcnJhbmdlKGRlc2MoYXZnX2xvZzJGQykpICU+JQogIHNsaWNlX2hlYWQobiA9IEluZikgJT4lICAjIGFsbCBzaWcgbWFya2VycwogIHN1bW1hcmlzZShtYXJrZXJzX2FsbCA9IHBhc3RlKGdlbmUsIGNvbGxhcHNlID0gIjsgIiksIC5ncm91cHMgPSAiZHJvcCIpCgojIE1hcCB0byBhbGwgY2VsbHMKcmVmZXJlbmNlX2ludGVncmF0ZWQkbWFya2Vyc19zaWdfYWxsIDwtIG1hcmtlcl9saXN0X2FsbCRtYXJrZXJzX2FsbFsKICBtYXRjaChyZWZlcmVuY2VfaW50ZWdyYXRlZCRzZXVyYXRfY2x1c3RlcnMsIG1hcmtlcl9saXN0X2FsbCRjbHVzdGVyKQpdCgojIOKUgOKUgCBUT1AgMjUgcGVyIGNsdXN0ZXIg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACm1hcmtlcl9saXN0X3RvcDI1IDwtIHRvcDI1X21hcmtlcnMgJT4lCiAgZ3JvdXBfYnkoY2x1c3RlcikgJT4lCiAgYXJyYW5nZShkZXNjKGF2Z19sb2cyRkMpKSAlPiUKICBzbGljZV9oZWFkKG4gPSAyNSkgJT4lCiAgc3VtbWFyaXNlKG1hcmtlcnNfdG9wMjUgPSBwYXN0ZShnZW5lLCBjb2xsYXBzZSA9ICI7ICIpLCAuZ3JvdXBzID0gImRyb3AiKQoKcmVmZXJlbmNlX2ludGVncmF0ZWQkbWFya2Vyc190b3AyNSA8LSBtYXJrZXJfbGlzdF90b3AyNSRtYXJrZXJzX3RvcDI1WwogIG1hdGNoKHJlZmVyZW5jZV9pbnRlZ3JhdGVkJHNldXJhdF9jbHVzdGVycywgbWFya2VyX2xpc3RfdG9wMjUkY2x1c3RlcikKXQoKIyDilIDilIAgVE9QIDUgcGVyIGNsdXN0ZXIg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACm1hcmtlcl9saXN0X3RvcDUgPC0gdG9wNV9tYXJrZXJzICU+JQogIGdyb3VwX2J5KGNsdXN0ZXIpICU+JQogIGFycmFuZ2UoZGVzYyhhdmdfbG9nMkZDKSkgJT4lCiAgc2xpY2VfaGVhZChuID0gNSkgJT4lCiAgc3VtbWFyaXNlKG1hcmtlcnNfdG9wNSA9IHBhc3RlKGdlbmUsIGNvbGxhcHNlID0gIjsgIiksIC5ncm91cHMgPSAiZHJvcCIpCgpyZWZlcmVuY2VfaW50ZWdyYXRlZCRtYXJrZXJzX3RvcDUgPC0gbWFya2VyX2xpc3RfdG9wNSRtYXJrZXJzX3RvcDVbCiAgbWF0Y2gocmVmZXJlbmNlX2ludGVncmF0ZWQkc2V1cmF0X2NsdXN0ZXJzLCBtYXJrZXJfbGlzdF90b3A1JGNsdXN0ZXIpCl0KCiMg4pSA4pSAIFN0b3JlIGZ1bGwgZGF0YS5mcmFtZXMgYXMgb2JqZWN0IGxpc3RzIChmb3IgZWFzeSByZXRyaWV2YWwpIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgApyZWZlcmVuY2VfaW50ZWdyYXRlZEBtaXNjJG1hcmtlcnNfYWxsX2ZpbHRlcmVkIDwtIHJlZl9tYXJrZXJzX2ZpbHRlcmVkCnJlZmVyZW5jZV9pbnRlZ3JhdGVkQG1pc2MkbWFya2Vyc190b3AyNSA8LSB0b3AyNV9tYXJrZXJzCnJlZmVyZW5jZV9pbnRlZ3JhdGVkQG1pc2MkbWFya2Vyc190b3A1IDwtIHRvcDVfbWFya2VycwoKY2F0KCLinIUgTWFya2VyIGxpc3RzIGFkZGVkIHRvIG9iamVjdCBtZXRhZGF0YTpcbiIpCmNhdCgiLSBtYXJrZXJzX3NpZ19hbGwgKGFsbCBzaWcgbWFya2VycyBwZXIgY2x1c3RlcilcbiIpCmNhdCgiLSBtYXJrZXJzX3RvcDI1ICh0b3AgMjUgcGVyIGNsdXN0ZXIpXG4iKQpjYXQoIi0gbWFya2Vyc190b3A1ICh0b3AgNSBwZXIgY2x1c3RlcilcbiIpCmNhdCgiLSBAbWlzYyRtYXJrZXJzXyogZGF0YS5mcmFtZXNcbiIpCgojIOKUgOKUgCBRdWljayB2ZXJpZmljYXRpb24g4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACmhlYWQocmVmZXJlbmNlX2ludGVncmF0ZWRAbWV0YS5kYXRhWywgZ3JlcGwoIm1hcmtlcnMiLCBjb2xuYW1lcyhyZWZlcmVuY2VfaW50ZWdyYXRlZEBtZXRhLmRhdGEpKV0pCnRhYmxlKG5jaGFyKHJlZmVyZW5jZV9pbnRlZ3JhdGVkJG1hcmtlcnNfdG9wNSkgPiAwKSAgIyBzaG91bGQgYWxsIGJlIFRSVUUKCmBgYAoKIyBRdWljayBWaXN1YWwgU3VtbWFyeSBvZiBUb3AgTWFya2VycyhIZWF0bWFwKQpgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTh9CiMgRG9IZWF0bWFwIG9mIHRvcCA1IHBlciBjbHVzdGVyIGZvciBxdWljayBiaW9sb2dpY2FsIHZhbGlkYXRpb24KbGlicmFyeShSQ29sb3JCcmV3ZXIpCgp0b3A1X2dlbmVzIDwtIHRvcDVfbWFya2VycyAlPiUKICBncm91cF9ieShjbHVzdGVyKSAlPiUKICBzbGljZV9oZWFkKG4gPSA1KSAlPiUKICBwdWxsKGdlbmUpICU+JQogIHVuaXF1ZSgpCgpEZWZhdWx0QXNzYXkocmVmZXJlbmNlX2ludGVncmF0ZWQpIDwtICJSTkEiCkRvSGVhdG1hcCgKICByZWZlcmVuY2VfaW50ZWdyYXRlZCwKICBmZWF0dXJlcyA9IHRvcDVfZ2VuZXMsCiAgZ3JvdXAuYnkgPSAic2V1cmF0X2NsdXN0ZXJzIiwKICBsYWJlbCAgICA9IFRSVUUsCiAgcmFzdGVyICAgPSBGQUxTRQopICsKICBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvcnMgPSByZXYoYnJld2VyLnBhbCgxMSwgIlJkQnUiKSkpICsKICBnZ3RpdGxlKCJUb3AgNSBtYXJrZXJzIHBlciBjbHVzdGVyIChyZXMuMC4zKSDigJQgcHJlLWFubm90YXRpb24gdmFsaWRhdGlvbiIpCgpgYGAKCgoKIyBRdWljayBWaXN1YWwgU3VtbWFyeSBvZiBUb3AgTWFya2VycyhEb3RwbG90KQpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTh9CiMg4pSA4pSAIFNDcHViciBEb3RQbG90IGZvciB0b3AgNSBtYXJrZXJzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgApEZWZhdWx0QXNzYXkocmVmZXJlbmNlX2ludGVncmF0ZWQpIDwtICJSTkEiCgojIEdldCB0b3AgNSBnZW5lcyBwZXIgY2x1c3RlciAoYWxyZWFkeSBjb21wdXRlZCkKdG9wNV9nZW5lcyA8LSB0b3A1X21hcmtlcnMgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGNsdXN0ZXIpICU+JQogIGRwbHlyOjpzbGljZV9oZWFkKG4gPSA1KSAlPiUKICBkcGx5cjo6cHVsbChnZW5lKSAlPiUKICB1bmlxdWUoKQoKIyDilIDilIAgU0NwdWJyIHYxLngg4oCUIG1pbmltYWwgd29ya2luZyBzeW50YXgg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgpTQ3B1YnI6OmRvX0RvdFBsb3Qoc2FtcGxlID0gcmVmZXJlbmNlX2ludGVncmF0ZWQsIAogICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSB0b3A1X2dlbmVzLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGZsaXAgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgKQoKYGBgCgojIFF1aWNrIFZpc3VhbCBTdW1tYXJ5IG9mIFRvcCBNYXJrZXJzKERvdHBsb3QpCmBgYHtyLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9MTJ9CiMgQ2hlY2sgY2x1c3RlciAwIGltbWVkaWF0ZWx5CkRlZmF1bHRBc3NheShyZWZlcmVuY2VfaW50ZWdyYXRlZCkgPC0gIlJOQSIKCiMgTXllbG9pZCB2cyBUIGNlbGwgbWFya2VycwpWbG5QbG90KHJlZmVyZW5jZV9pbnRlZ3JhdGVkLCAKICAgICAgICBmZWF0dXJlcyA9IGMoIkFJRjEiLCAiQ0QzRCIsICJDRDQiLCAiQ0QxNCIsICJDRDY4IiwgIkxZWiIpLAogICAgICAgIGdyb3VwLmJ5ID0gInNldXJhdF9jbHVzdGVycyIsIHB0LnNpemUgPSAwKQoKIyBBemltdXRoIHByZWRpY3RlZCBsYWJlbHMgaW4gY2x1c3RlciAwCnRhYmxlKHJlZmVyZW5jZV9pbnRlZ3JhdGVkJHByZWRpY3RlZC5jZWxsdHlwZS5sMlsKICByZWZlcmVuY2VfaW50ZWdyYXRlZCRzZXVyYXRfY2x1c3RlcnMgPT0gIjAiXSkKCmBgYAoKIyBGZWF0dXJlUGxvdCB0byBjaGVjayBURU1SQQpgYGB7ciwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTEyfQpGZWF0dXJlUGxvdCgKICBvYmplY3QgPSByZWZlcmVuY2VfaW50ZWdyYXRlZCwKICBmZWF0dXJlcyA9IGMoIlBSRjEiLCAiTktHNyIsICJGR0ZCUDIiLCAiR05MWSIpLAogIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAicmVkIiksbGFiZWwgPSBULAogIG5jb2wgPSAyCikKCmBgYCAKCiMgUiBBbm5vdGF0aW9uIENvZGUKYGBge3IsIGZpZy53aWR0aD0xNCwgZmlnLmhlaWdodD01fQojICMgQ09NUExFVEUgQ0VMTCBUWVBFIEFOTk9UQVRJT04gQ09ERQojIAojICMgU3RlcCAxOiBFbnN1cmUgY2x1c3RlcnMgYXJlIGNoYXJhY3RlcnMKIyByZWZlcmVuY2VfaW50ZWdyYXRlZCRzZXVyYXRfY2x1c3RlcnMgPC0gYXMuY2hhcmFjdGVyKHJlZmVyZW5jZV9pbnRlZ3JhdGVkJHNldXJhdF9jbHVzdGVycykKIyAKIyAjIFN0ZXAgMjogRGVmaW5lIG5hbWVkIGxhYmVscyAoZXhhY3RseSBtYXRjaGVzIHlvdXIgMC02IGNsdXN0ZXJzKQojIGNsdXN0ZXJfbGFiZWxzIDwtIGMoCiMgICAiMCIgPSAiQ0Q0IFRuYWl2ZSAoQ0NSNytTRUxMK1RDRjcrKSIsICAgICAgICAgIyBDYW5vbmljYWwgbmFpdmUKIyAgICIxIiA9ICJDRDQgVENNIChDRDE2MSsvSUw3UispIiwgICAgICAgICAgICAgICAjIFRDTSwgS0xSQjEvQ0QxNjErIFRoMTctbWVtb3J5CiMgICAiMiIgPSAiQ0Q0IFRDTSAoQ0NSNCsvVGgyLWxpa2UpIiwgICAgICAgICAgICAgIyBBY3RpdmF0ZWQgVENNLCBDQ1I0K0JBVEYrCiMgICAiMyIgPSAiQ0Q0IENUTC9UZW1yYSAoR1pNSytHWk1BK0NDTDUrKSIsICAgICAjIEN5dG90b3hpYyBlZmZlY3RvciwgVGVtcmEKIyAgICI0IiA9ICJDRDQgVEVNIChORi1rQiBhY3RpdmF0ZWQpIiwgICAgICAgICAgICAjIEVmZmVjdG9yIG1lbW9yeSwgc3RyZXNzLWFjdGl2YXRlZAojICAgIjUiID0gIkNENCBUcmVnIChGT1hQMytIZWxpb3MrQ0QyNSspIiwgICAgICAgICMgTmF0dXJhbCB0VHJlZywgaGlnaGVzdCBjb25maWRlbmNlCiMgICAiNiIgPSAiQ0Q0IFRuYWl2ZS1SVEUgKElHRjFSKykiICAgICAgICAgICAgICAjIFF1aWVzY2VudC9SVEUgbmFpdmUgc3Vic2V0IChRdWllc2NlbnQvUlRFIG5haXZlIHN1YnNldCkKIyApCiMgCiMgIyBTdGVwIDM6IENyZWF0ZSB2ZWN0b3IgKHlvdSBhbHJlYWR5IHZlcmlmaWVkIHRoaXMgd29ya3MpCiMgY2x1c3RlcnNfY2hyIDwtIGFzLmNoYXJhY3RlcihyZWZlcmVuY2VfaW50ZWdyYXRlZCRzZXVyYXRfY2x1c3RlcnMpCiMgY2VsbF90eXBlX3ZlYyA8LSBjbHVzdGVyX2xhYmVsc1tjbHVzdGVyc19jaHJdCiMgCiMgIyBTdGVwIDQ6IFNBRkUgQVNTSUdOTUVOVCAoZGlyZWN0IG1ldGFkYXRhIHdyaXRlKQojIHJlZmVyZW5jZV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRjZWxsX3R5cGUgPC0gY2VsbF90eXBlX3ZlYwojIAojICMgU3RlcCA1OiBGb3JjZSBmYWN0b3Igb3JkZXJpbmcgMOKGkjYgZm9yIHBsb3RzCiMgcmVmZXJlbmNlX2ludGVncmF0ZWRAbWV0YS5kYXRhJGNlbGxfdHlwZSA8LSBmYWN0b3IoCiMgICByZWZlcmVuY2VfaW50ZWdyYXRlZEBtZXRhLmRhdGEkY2VsbF90eXBlLAojICAgbGV2ZWxzID0gYygiQ0Q0IFRuYWl2ZSAoQ0NSNytTRUxMK1RDRjcrKSIsCiMgICAgICAgICAgICAgICJDRDQgVENNIChDRDE2MSsvSUw3UispIiwKIyAgICAgICAgICAgICAgIkNENCBUQ00gKENDUjQrL1RoMi1saWtlKSIsCiMgICAgICAgICAgICAgICJDRDQgQ1RML1RlbXJhIChHWk1LK0daTUErQ0NMNSspIiwKIyAgICAgICAgICAgICAgIkNENCBURU0gKE5GLWtCIGFjdGl2YXRlZCkiLAojICAgICAgICAgICAgICAiQ0Q0IFRyZWcgKEZPWFAzK0hlbGlvcytDRDI1KykiLAojICAgICAgICAgICAgICAiQ0Q0IFRuYWl2ZS1SVEUgKElHRjFSKykiKQojICkKCiMgU3RlcCA2OiBWQUxJREFURQpjYXQoIuKchSBBc3NpZ25tZW50IGNvbXBsZXRlIVxuIikKcHJpbnQodGFibGUocmVmZXJlbmNlX2ludGVncmF0ZWQkc2V1cmF0X2NsdXN0ZXJzLCByZWZlcmVuY2VfaW50ZWdyYXRlZEBtZXRhLmRhdGEkY2VsbF90eXBlKSkKcHJpbnQodGFibGUocmVmZXJlbmNlX2ludGVncmF0ZWRAbWV0YS5kYXRhJGNlbGxfdHlwZSkpCgojIFN0ZXAgNzogVklTVUFMSVpFCnAxIDwtIERpbVBsb3QocmVmZXJlbmNlX2ludGVncmF0ZWQsIGdyb3VwLmJ5ID0gInNldXJhdF9jbHVzdGVycyIsIAogICAgICAgICAgICAgIGxhYmVsID0gVFJVRSwgbGFiZWwuc2l6ZSA9IDMpICsgZ2d0aXRsZSgiQ2x1c3RlcnMgMC02IikKcDIgPC0gRGltUGxvdChyZWZlcmVuY2VfaW50ZWdyYXRlZCwgZ3JvdXAuYnkgPSAiY2VsbF90eXBlIiwgCiAgICAgICAgICAgICAgbGFiZWwgPSBUUlVFLCByZXBlbCA9IFRSVUUsIGxhYmVsLnNpemUgPSAzKSArIGdndGl0bGUoIkNlbGwgVHlwZXMiKQoKcDEgfCBwMgoKIyBTdGVwIDg6IENyb3NzLWNoZWNrIHdpdGggQXppbXV0aAp0YWJsZShyZWZlcmVuY2VfaW50ZWdyYXRlZCRwcmVkaWN0ZWQuY2VsbHR5cGUubDIsIAogICAgICByZWZlcmVuY2VfaW50ZWdyYXRlZEBtZXRhLmRhdGEkY2VsbF90eXBlKQpgYGAKCiMgU2F2ZSBhbm5vdGF0ZWQgcmVmZXJlbmNlCmBgYHtyfQojIFNhdmUgYW5ub3RhdGVkIHJlZmVyZW5jZQpzYXZlUkRTKHJlZmVyZW5jZV9pbnRlZ3JhdGVkLCAiQ0Q0X3JlZmVyZW5jZV9hbm5vdGF0ZWRfd2l0aF9tYXJrZXJzLnJkcyIpCmNhdCgi4pyFIEFubm90YXRlZCByZWZlcmVuY2UgIHdpdGggbWFya2VycyBzYXZlZCFcbiIpCgpgYGAgCg==