Load RDS with all
annotations
Idents(All_samples_Merged) <- "seurat_clusters"
# Ensure clusters are ordered 0-13
All_samples_Merged$seurat_clusters <- factor(All_samples_Merged$seurat_clusters,
levels = as.character(0:13))
cat("Total cells:", ncol(All_samples_Merged), "\n")
Total cells: 49305
cat("Total clusters:", length(unique(All_samples_Merged$seurat_clusters)), "\n\n")
Total clusters: 14
cat("Clusters (ordered 0-18):\n")
Clusters (ordered 0-18):
print(table(All_samples_Merged$seurat_clusters))
0 1 2 3 4 5 6 7 8 9 10 11 12 13
6789 5275 4663 4661 4086 3634 3536 3409 3338 3273 3212 1675 1063 691
cat("\n\nSamples (ordered):\n")
Samples (ordered):
print(table(All_samples_Merged$cell_line))
L1 L2 L3 L4 L5 L6 L7 CD4Tcells_lab
5825 5935 6428 6006 6022 5148 5331 5106
CD4Tcells_10x
3504
Create Annotation
Summary Table
Create Annotation
Summary Table by Clusters
library(dplyr)
library(knitr)
library(kableExtra)
# Extract dominant label per cluster per method
cols_needed <- c("seurat_clusters", "predicted.id", "predicted.celltype.l2",
"singler.immune", "scATOMIC_annotation")
annotation_table_cluster <- All_samples_Merged@meta.data[, cols_needed] %>%
group_by(seurat_clusters) %>%
summarise(
scPred = names(sort(table(predicted.id), decreasing = TRUE))[1],
Azimuth = names(sort(table(predicted.celltype.l2), decreasing = TRUE))[1],
SingleR = names(sort(table(singler.immune), decreasing = TRUE))[1],
scATOMIC = names(sort(table(scATOMIC_annotation), decreasing = TRUE))[1],
n_cells = n(),
.groups = "drop"
) %>%
arrange(seurat_clusters)
# Add consensus column
annotation_table_cluster <- annotation_table_cluster %>%
mutate(
Consensus = (scPred == Azimuth & Azimuth == SingleR & SingleR == scATOMIC)
)
# Display
cat("\n=== ANNOTATION BY CLUSTER ===\n\n")
=== ANNOTATION BY CLUSTER ===
kable(annotation_table_cluster, caption = "Dominant Cell Type Annotation per Cluster") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 10) %>%
column_spec(7, background = ifelse(annotation_table_cluster$Consensus, "#d4edda", "#f8d7da"))
Dominant Cell Type Annotation per Cluster
| seurat_clusters |
scPred |
Azimuth |
SingleR |
scATOMIC |
n_cells |
Consensus |
| 0 |
Plasma cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Lung Cancer Cell |
6789 |
FALSE |
| 1 |
CD4 T cell |
NK Proliferating |
NK cells |
Effector/Memory CD4+ T cells |
5275 |
FALSE |
| 2 |
Plasma cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Lung Cancer Cell |
4663 |
FALSE |
| 3 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, naive |
Naive CD4+ T cells |
4661 |
FALSE |
| 4 |
Plasma cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Lung Cancer Cell |
4086 |
FALSE |
| 5 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
3634 |
FALSE |
| 6 |
Plasma cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Lung Cancer Cell |
3536 |
FALSE |
| 7 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
3409 |
FALSE |
| 8 |
Plasma cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Lung Cancer Cell |
3338 |
FALSE |
| 9 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
3273 |
FALSE |
| 10 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, TFH |
Naive CD4+ T cells |
3212 |
FALSE |
| 11 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
1675 |
FALSE |
| 12 |
Plasma cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
1063 |
FALSE |
| 13 |
Plasma cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Lung Cancer Cell |
691 |
FALSE |
# Save
write.csv(annotation_table_cluster, "CellLines_annotation_BY_CLUSTER_04-02-2026.csv",
row.names = FALSE)
cat("\n✓ Annotation table by cluster saved\n")
✓ Annotation table by cluster saved
Create Annotation
Summary Table - BY CELL LINE
library(dplyr)
library(knitr)
library(kableExtra)
# Extract dominant label per CELL LINE per method
cols_needed <- c("cell_line", "predicted.id", "predicted.celltype.l2",
"singler.immune", "scATOMIC_annotation")
annotation_table_cellline <- All_samples_Merged@meta.data[, cols_needed] %>%
group_by(cell_line) %>%
summarise(
scPred = names(sort(table(predicted.id), decreasing = TRUE))[1],
Azimuth = names(sort(table(predicted.celltype.l2), decreasing = TRUE))[1],
SingleR = names(sort(table(singler.immune), decreasing = TRUE))[1],
scATOMIC = names(sort(table(scATOMIC_annotation), decreasing = TRUE))[1],
n_cells = n(),
.groups = "drop"
) %>%
arrange(cell_line)
# Add consensus column
annotation_table_cellline <- annotation_table_cellline %>%
mutate(
Consensus = (scPred == Azimuth & Azimuth == SingleR & SingleR == scATOMIC)
)
# Display
cat("\n=== ANNOTATION BY CELL LINE ===\n\n")
=== ANNOTATION BY CELL LINE ===
kable(annotation_table_cellline, caption = "Dominant Cell Type Annotation per Cell Line") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 10) %>%
column_spec(7, background = ifelse(annotation_table_cellline$Consensus, "#d4edda", "#f8d7da"))
Dominant Cell Type Annotation per Cell Line
| cell_line |
scPred |
Azimuth |
SingleR |
scATOMIC |
n_cells |
Consensus |
| L1 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, Th2 |
Effector/Memory CD4+ T cells |
5825 |
FALSE |
| L2 |
CD4 T cell |
NK Proliferating |
NK cells |
Effector/Memory CD4+ T cells |
5935 |
FALSE |
| L3 |
Plasma cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Lung Cancer Cell |
6428 |
FALSE |
| L4 |
Plasma cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Lung Cancer Cell |
6006 |
FALSE |
| L5 |
Plasma cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Lung Cancer Cell |
6022 |
FALSE |
| L6 |
cDC |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
5148 |
FALSE |
| L7 |
Plasma cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Lung Cancer Cell |
5331 |
FALSE |
| CD4Tcells_lab |
CD4 T cell |
CD4 TCM |
T cells, CD4+, TFH |
Naive CD4+ T cells |
5106 |
FALSE |
| CD4Tcells_10x |
CD4 T cell |
CD4 TCM |
T cells, CD4+, naive |
Naive CD4+ T cells |
3504 |
FALSE |
# Save
write.csv(annotation_table_cellline, "CellLines_annotation_BY_CELLLINE_04-02-2026.csv",
row.names = FALSE)
cat("\n✓ Annotation table by cell line saved\n")
✓ Annotation table by cell line saved
Create Annotation
Summary Table - Combined Table (Cluster × Cell Line)
library(dplyr)
library(knitr)
library(kableExtra)
# Extract dominant label per CLUSTER × CELL LINE × METHOD
cols_needed <- c("seurat_clusters", "cell_line", "predicted.id", "predicted.celltype.l2",
"singler.immune", "scATOMIC_annotation")
annotation_table_combined <- All_samples_Merged@meta.data[, cols_needed] %>%
group_by(seurat_clusters, cell_line) %>%
summarise(
scPred = names(sort(table(predicted.id), decreasing = TRUE))[1],
Azimuth = names(sort(table(predicted.celltype.l2), decreasing = TRUE))[1],
SingleR = names(sort(table(singler.immune), decreasing = TRUE))[1],
scATOMIC = names(sort(table(scATOMIC_annotation), decreasing = TRUE))[1],
n_cells = n(),
.groups = "drop"
) %>%
arrange(cell_line, seurat_clusters)
# Add consensus column
annotation_table_combined <- annotation_table_combined %>%
mutate(
Consensus = (scPred == Azimuth & Azimuth == SingleR & SingleR == scATOMIC)
)
# Display (first 20 rows)
cat("\n=== ANNOTATION BY CLUSTER × CELL LINE (First 20 rows) ===\n\n")
=== ANNOTATION BY CLUSTER × CELL LINE (First 20 rows) ===
kable(head(annotation_table_combined, 20),
caption = "Dominant Annotation per Cluster × Cell Line") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE, font_size = 9) %>%
column_spec(8, background = ifelse(head(annotation_table_combined$Consensus, 20),
"#d4edda", "#f8d7da"))
Dominant Annotation per Cluster × Cell Line
| seurat_clusters |
cell_line |
scPred |
Azimuth |
SingleR |
scATOMIC |
n_cells |
Consensus |
| 0 |
L1 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
7 |
FALSE |
| 1 |
L1 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
91 |
FALSE |
| 2 |
L1 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, Th2 |
Effector/Memory CD4+ T cells |
101 |
FALSE |
| 4 |
L1 |
B cell |
CD4 TCM |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
1 |
FALSE |
| 5 |
L1 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, Th2 |
Effector/Memory CD4+ T cells |
2514 |
FALSE |
| 6 |
L1 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, Th2 |
Effector/Memory CD4+ T cells |
44 |
FALSE |
| 7 |
L1 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
518 |
FALSE |
| 9 |
L1 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, Th2 |
Effector/Memory CD4+ T cells |
2052 |
FALSE |
| 10 |
L1 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, Th1 |
Effector/Memory CD4+ T cells |
8 |
FALSE |
| 11 |
L1 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, Th2 |
Effector/Memory CD4+ T cells |
406 |
FALSE |
| 12 |
L1 |
CD4 T cell |
CD4 TCM |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
82 |
FALSE |
| 13 |
L1 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, Th2 |
Effector/Memory CD4+ T cells |
1 |
FALSE |
| 0 |
L2 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
2 |
FALSE |
| 1 |
L2 |
CD4 T cell |
NK Proliferating |
NK cells |
Effector/Memory CD4+ T cells |
5113 |
FALSE |
| 2 |
L2 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
13 |
FALSE |
| 4 |
L2 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
1 |
FALSE |
| 5 |
L2 |
CD4 T cell |
NK Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
99 |
FALSE |
| 6 |
L2 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
7 |
FALSE |
| 7 |
L2 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
189 |
FALSE |
| 9 |
L2 |
CD4 T cell |
CD4 Proliferating |
T cells, CD4+, memory TREG |
Effector/Memory CD4+ T cells |
487 |
FALSE |
# Save
write.csv(annotation_table_combined,
"CellLines_annotation_BY_CLUSTER_AND_CELLLINE_04-02-2026.csv",
row.names = FALSE)
cat("\n✓ Combined annotation table (cluster × cell line) saved\n")
✓ Combined annotation table (cluster × cell line) saved
cat("Total rows:", nrow(annotation_table_combined), "\n")
Total rows: 103
Create Annotation
Summary
library(purrr) # Make sure this is loaded
# Define annotation methods
methods <- c(
"predicted.id", # scPred
"predicted.celltype.l2", # Azimuth (l2 prediction)
"singler.immune", # SingleR
"scATOMIC_annotation" # scATOMIC
)
# Create summary - most common label per cluster for each method
annotation_summary <- map_dfr(methods, function(m) {
df <- All_samples_Merged@meta.data
df %>%
filter(!is.na(.data[[m]])) %>%
group_by(seurat_clusters, label = .data[[m]]) %>%
summarise(n = n(), .groups = "drop") %>%
group_by(seurat_clusters) %>%
slice_max(n, n = 1, with_ties = FALSE) %>%
mutate(method = m)
})
# Rename methods for display
annotation_summary <- annotation_summary %>%
mutate(method = recode(method,
"predicted.id" = "scPred",
"predicted.celltype.l2" = "Azimuth.l2",
"singler.immune" = "SingleR(Immune)",
"scATOMIC_annotation" = "scATOMIC"))
# Set method order
annotation_summary$method <- factor(annotation_summary$method,
levels = c("scPred", "Azimuth.l2",
"SingleR(Immune)", "scATOMIC"))
# Ensure cluster order
annotation_summary$seurat_clusters <- factor(annotation_summary$seurat_clusters,
levels = as.character(0:13)) # Adjust range
Summary
Statistics
# Cell counts per method
cat("\n=== Cells Annotated Per Method ===\n\n")
=== Cells Annotated Per Method ===
method_cols <- c("predicted.id", "predicted.celltype.l2", "singler.immune", "scATOMIC_annotation")
for(m in method_cols){
n_annotated <- sum(!is.na(All_samples_Merged@meta.data[[m]]))
cat(sprintf("%-25s: %5d cells (%.1f%%)\n",
m, n_annotated, n_annotated/ncol(All_samples_Merged)*100))
}
predicted.id : 49305 cells (100.0%)
predicted.celltype.l2 : 49305 cells (100.0%)
singler.immune : 49272 cells (99.9%)
scATOMIC_annotation : 49305 cells (100.0%)
# Number of unique labels per method
cat("\n=== Unique Labels Per Method ===\n\n")
=== Unique Labels Per Method ===
annotation_summary %>%
group_by(method) %>%
summarise(n_unique_labels = n_distinct(label)) %>%
print()
# A tibble: 4 × 2
method n_unique_labels
<fct> <int>
1 scPred 2
2 Azimuth.l2 3
3 SingleR(Immune) 4
4 scATOMIC 3
Basic Heatmap
Visualization
library(ggplot2)
ggplot(annotation_summary, aes(x = seurat_clusters, y = method, fill = label)) +
geom_tile(color = "white", linewidth = 0.5) +
scale_fill_discrete() +
labs(
x = "Seurat Clusters",
y = "Annotation Method",
fill = "Assigned Cell Type",
title = "Cross-Method Comparison of Cell Type Annotations"
) +
theme_minimal(base_size = 14) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
axis.title = element_text(face = "bold"),
plot.title = element_text(hjust = 0.5, face = "bold", size = 16)
)

Enhanced Heatmap with
Custom Colors
library(RColorBrewer)
# Generate color palette
num_colors <- length(unique(annotation_summary$label))
my_colors <- colorRampPalette(brewer.pal(8, "Set2"))(num_colors)
p_heatmap <- ggplot(annotation_summary, aes(x = seurat_clusters, y = method, fill = label)) +
geom_tile(color = "white", linewidth = 0.5) +
scale_fill_manual(values = my_colors) +
scale_x_discrete(drop = FALSE) + # Keep all cluster levels
labs(
x = "Seurat Clusters",
y = "Annotation Method",
fill = "Assigned Cell Type",
title = "Cross-Method Comparison of Cell Type Annotations"
) +
theme_minimal(base_size = 14) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
axis.title = element_text(face = "bold"),
plot.title = element_text(hjust = 0.5, face = "bold", size = 16),
legend.position = "right"
)
print(p_heatmap)

# Save high-quality versions
ggsave("CellLines_annotation_heatmap_04-02-2026.png", plot = p_heatmap,
width = 14, height = 5, dpi = 300, bg = "white")
ggsave("CellLines_annotation_heatmap_04-02-2026.pdf", plot = p_heatmap,
width = 14, height = 5)
cat("\n✓ Heatmap saved as PNG and PDF\n")
✓ Heatmap saved as PNG and PDF
MANUSCRIPT FIGURE
MANUSCRIPT FIGURE:
Text-Based Heatmap BY CLUSTERS
library(ggplot2)
library(dplyr)
library(tidyr)
# Prepare data by CLUSTERS
cols_needed <- c("seurat_clusters", "predicted.id", "predicted.celltype.l2",
"singler.immune", "scATOMIC_annotation")
heatmap_data_clusters <- All_samples_Merged@meta.data[, cols_needed] %>%
group_by(seurat_clusters) %>%
summarise(
scPred = names(sort(table(predicted.id), decreasing = TRUE))[1],
Azimuth = names(sort(table(predicted.celltype.l2), decreasing = TRUE))[1],
SingleR = names(sort(table(singler.immune), decreasing = TRUE))[1],
scATOMIC = names(sort(table(scATOMIC_annotation), decreasing = TRUE))[1],
.groups = "drop"
) %>%
pivot_longer(cols = c(scPred, Azimuth, SingleR, scATOMIC),
names_to = "Method", values_to = "CellType") %>%
mutate(
Method = factor(Method, levels = c("scATOMIC", "SingleR", "Azimuth", "scPred")),
seurat_clusters = factor(seurat_clusters, levels = as.character(0:13)), # CHANGE RANGE
CellType_display = ifelse(nchar(CellType) > 20,
paste0(substr(CellType, 1, 17), "..."),
CellType)
)
# Create manuscript figure BY CLUSTERS
p_manuscript_clusters <- ggplot(heatmap_data_clusters,
aes(x = seurat_clusters, y = Method, label = CellType_display)) +
geom_tile(color = "grey20", fill = "white", linewidth = 0.8) +
geom_text(size = 3, hjust = 0.5, vjust = 0.5, fontface = "bold") +
labs(
title = "Discordance in Automated Cell Type Annotation - Cell Lines by Clusters",
x = "Seurat Cluster",
y = "Annotation Method"
) +
theme_minimal(base_size = 16) +
theme(
axis.text.x = element_text(face = "bold", size = 14),
axis.text.y = element_text(face = "bold", size = 14),
axis.title = element_text(size = 16, face = "bold"),
plot.title = element_text(hjust = 0.5, face = "bold", size = 18),
panel.grid = element_blank(),
plot.margin = margin(10, 10, 10, 10)
)
print(p_manuscript_clusters)

# Save high-quality versions
ggsave("FigureX_CellLines_Annotation_BY_CLUSTERS.png",
plot = p_manuscript_clusters, width = 30, height = 5, dpi = 600, bg = "white")
ggsave("FigureX_CellLines_Annotation_BY_CLUSTERS.pdf",
plot = p_manuscript_clusters, width = 30, height = 5)
ggsave("FigureX_CellLines_Annotation_BY_CLUSTERS.tiff",
plot = p_manuscript_clusters, width = 30, height = 5, dpi = 600, bg = "white")
cat("\n✓ Manuscript figure BY CLUSTERS saved (PNG, PDF, TIFF at 600 DPI)\n")
✓ Manuscript figure BY CLUSTERS saved (PNG, PDF, TIFF at 600 DPI)
MANUSCRIPT FIGURE:
Text-Based Heatmap BY CELL LINES
# Prepare data by CELL LINES
cols_needed_cellline <- c("cell_line", "predicted.id", "predicted.celltype.l2",
"singler.immune", "scATOMIC_annotation")
heatmap_data_celllines <- All_samples_Merged@meta.data[, cols_needed_cellline] %>%
group_by(cell_line) %>%
summarise(
scPred = names(sort(table(predicted.id), decreasing = TRUE))[1],
Azimuth = names(sort(table(predicted.celltype.l2), decreasing = TRUE))[1],
SingleR = names(sort(table(singler.immune), decreasing = TRUE))[1],
scATOMIC = names(sort(table(scATOMIC_annotation), decreasing = TRUE))[1],
.groups = "drop"
) %>%
pivot_longer(cols = c(scPred, Azimuth, SingleR, scATOMIC),
names_to = "Method", values_to = "CellType") %>%
mutate(
Method = factor(Method, levels = c("scATOMIC", "SingleR", "Azimuth", "scPred")),
CellType_display = ifelse(nchar(CellType) > 20,
paste0(substr(CellType, 1, 17), "..."),
CellType)
)
# Create manuscript figure BY CELL LINES
p_manuscript_celllines <- ggplot(heatmap_data_celllines,
aes(x = cell_line, y = Method, label = CellType_display)) +
geom_tile(color = "grey20", fill = "white", linewidth = 0.8) +
geom_text(size = 3, hjust = 0.5, vjust = 0.5, fontface = "bold") +
labs(
title = "Discordance in Automated Cell Type Annotation - By Cell Line",
x = "Cell Line",
y = "Annotation Method"
) +
theme_minimal(base_size = 16) +
theme(
axis.text.x = element_text(face = "bold", size = 14, angle = 45, hjust = 1),
axis.text.y = element_text(face = "bold", size = 14),
axis.title = element_text(size = 16, face = "bold"),
plot.title = element_text(hjust = 0.5, face = "bold", size = 18),
panel.grid = element_blank(),
plot.margin = margin(10, 10, 10, 10)
)
print(p_manuscript_celllines)

# Save high-quality versions
ggsave("FigureX_CellLines_Annotation_BY_CELLLINE.png",
plot = p_manuscript_celllines, width = 14, height = 5, dpi = 600, bg = "white")
ggsave("FigureX_CellLines_Annotation_BY_CELLLINE.pdf",
plot = p_manuscript_celllines, width = 14, height = 5)
ggsave("FigureX_CellLines_Annotation_BY_CELLLINE.tiff",
plot = p_manuscript_celllines, width = 14, height = 5, dpi = 600, bg = "white")
cat("\n✓ Manuscript figure BY CELL LINE saved (PNG, PDF, TIFF at 600 DPI)\n")
✓ Manuscript figure BY CELL LINE saved (PNG, PDF, TIFF at 600 DPI)
COMBINED MANUSCRIPT
FIGURE: Clusters × Cell Lines (Optional)
# Prepare data by CLUSTER × CELL LINE
cols_needed_combined <- c("seurat_clusters", "cell_line", "predicted.id", "predicted.celltype.l2",
"singler.immune", "scATOMIC_annotation")
heatmap_data_combined <- All_samples_Merged@meta.data[, cols_needed_combined] %>%
group_by(seurat_clusters, cell_line) %>%
summarise(
scPred = names(sort(table(predicted.id), decreasing = TRUE))[1],
Azimuth = names(sort(table(predicted.celltype.l2), decreasing = TRUE))[1],
SingleR = names(sort(table(singler.immune), decreasing = TRUE))[1],
scATOMIC = names(sort(table(scATOMIC_annotation), decreasing = TRUE))[1],
.groups = "drop"
) %>%
pivot_longer(cols = c(scPred, Azimuth, SingleR, scATOMIC),
names_to = "Method", values_to = "CellType") %>%
mutate(
Method = factor(Method, levels = c("scATOMIC", "SingleR", "Azimuth", "scPred")),
seurat_clusters = factor(seurat_clusters, levels = as.character(0:13)),
combined_label = paste0("C", seurat_clusters, "_", cell_line),
CellType_display = ifelse(nchar(CellType) > 15,
paste0(substr(CellType, 1, 12), "..."),
CellType)
)
# Create combined figure
p_manuscript_combined <- ggplot(heatmap_data_combined,
aes(x = combined_label, y = Method, label = CellType_display)) +
geom_tile(color = "grey20", fill = "white", linewidth = 0.5) +
geom_text(size = 2, hjust = 0.5, vjust = 0.5, fontface = "bold") +
labs(
title = "Cell Type Annotation - By Cluster × Cell Line",
x = "Cluster_CellLine",
y = "Annotation Method"
) +
theme_minimal(base_size = 14) +
theme(
axis.text.x = element_text(face = "bold", size = 8, angle = 90, hjust = 1, vjust = 0.5),
axis.text.y = element_text(face = "bold", size = 12),
axis.title = element_text(size = 14, face = "bold"),
plot.title = element_text(hjust = 0.5, face = "bold", size = 16),
panel.grid = element_blank(),
plot.margin = margin(10, 10, 10, 10)
)
print(p_manuscript_combined)

# Save
ggsave("FigureX_CellLines_Annotation_CLUSTERS_x_CELLLINES.png",
plot = p_manuscript_combined, width = 24, height = 6, dpi = 600, bg = "white")
ggsave("FigureX_CellLines_Annotation_CLUSTERS_x_CELLLINES.pdf",
plot = p_manuscript_combined, width = 24, height = 6)
cat("\n✓ Combined manuscript figure (Cluster × Cell Line) saved\n")
✓ Combined manuscript figure (Cluster × Cell Line) saved
LS0tCnRpdGxlOiAiQ3Jvc3MtTWV0aG9kIEFubm90YXRpb24gQ29tcGFyaXNvbiAtIENlbGwgbGluZXMgU8OpemFyeSBTeW5kcm9tZSBEYXRhc2V0IgphdXRob3I6IE5hc2lyIE1haG1vb2QgQWJiYXNpCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IHRydWUKICAgIHRoZW1lOiBqb3VybmFsCi0tLQoKCgojIGxvYWQgbGlicmFyaWVzCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZWNobyA9IFRSVUUsCiAgd2FybmluZyA9IEZBTFNFLAogIG1lc3NhZ2UgPSBGQUxTRSwKICBmaWcud2lkdGggPSAxNCwKICBmaWcuaGVpZ2h0ID0gOCwKICBkcGkgPSAzMDAKKQojIGxvYWQgbGlicmFyaWVzCiAgICBsaWJyYXJ5KFNldXJhdCkKICAgIGxpYnJhcnkoZHBseXIpCiAgICBsaWJyYXJ5KHBhdGNod29yaykKICAgIGxpYnJhcnkoZ2dwbG90MikKICAgIGxpYnJhcnkocGhlYXRtYXApCiAgICBsaWJyYXJ5KHNjUHJlZCkKICAgIGxpYnJhcnkoY2VsbGRleCkKICAgIGxpYnJhcnkoU2luZ2xlUikKICAgIGxpYnJhcnkocmVtb3RlcykKICAgIGxpYnJhcnkocHJlc3RvKQogICAgbGlicmFyeShTZXVyYXREaXNrKQogICAgbGlicmFyeShTZXVyYXREYXRhKQogICAgbGlicmFyeShBemltdXRoKQoKYGBgCgoKCgojIExvYWQgUkRTIHdpdGggYWxsIGFubm90YXRpb25zCmBgYHtyfQojIExvYWQgbWFpbiBvYmplY3Qgd2l0aCBhbGwgYW5ub3RhdGlvbnMKQWxsX3NhbXBsZXNfTWVyZ2VkIDwtIHJlYWRSRFMoIi9ob21lL2Jpb2luZm8vMS1UaGVzaXNfRmluYWxfWWVhcl8yMDI1LzIwMjUtWWVhcjNfQW5hbHlzaXMvMS1zY1JOQV9SRVNVTFRTLTE5LTExLTIwMjUvMi1CZW5jaG1hcmtpbmctQW5ub3RhdGlvbl9tZXRob2RzXzA1LTExLTIwMjUvQWxsX3NhbXBsZXNfTWVyZ2VkX0JlbmNobWFya2VkXzA1LTExLTIwMjUucmRzIikKCgpJZGVudHMoQWxsX3NhbXBsZXNfTWVyZ2VkKSA8LSAic2V1cmF0X2NsdXN0ZXJzIgoKIyBFbnN1cmUgY2x1c3RlcnMgYXJlIG9yZGVyZWQgMC0xMwpBbGxfc2FtcGxlc19NZXJnZWQkc2V1cmF0X2NsdXN0ZXJzIDwtIGZhY3RvcihBbGxfc2FtcGxlc19NZXJnZWQkc2V1cmF0X2NsdXN0ZXJzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGFzLmNoYXJhY3RlcigwOjEzKSkKCgoKCmNhdCgiVG90YWwgY2VsbHM6IiwgbmNvbChBbGxfc2FtcGxlc19NZXJnZWQpLCAiXG4iKQpjYXQoIlRvdGFsIGNsdXN0ZXJzOiIsIGxlbmd0aCh1bmlxdWUoQWxsX3NhbXBsZXNfTWVyZ2VkJHNldXJhdF9jbHVzdGVycykpLCAiXG5cbiIpCmNhdCgiQ2x1c3RlcnMgKG9yZGVyZWQgMC0xOCk6XG4iKQpwcmludCh0YWJsZShBbGxfc2FtcGxlc19NZXJnZWQkc2V1cmF0X2NsdXN0ZXJzKSkKCmNhdCgiXG5cblNhbXBsZXMgKG9yZGVyZWQpOlxuIikKcHJpbnQodGFibGUoQWxsX3NhbXBsZXNfTWVyZ2VkJGNlbGxfbGluZSkpCgpgYGAKIyBDcmVhdGUgQW5ub3RhdGlvbiBTdW1tYXJ5IFRhYmxlCiMjIENyZWF0ZSBBbm5vdGF0aW9uIFN1bW1hcnkgVGFibGUgYnkgQ2x1c3RlcnMKYGBge3IgfQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCgojIEV4dHJhY3QgZG9taW5hbnQgbGFiZWwgcGVyIGNsdXN0ZXIgcGVyIG1ldGhvZApjb2xzX25lZWRlZCA8LSBjKCJzZXVyYXRfY2x1c3RlcnMiLCAicHJlZGljdGVkLmlkIiwgInByZWRpY3RlZC5jZWxsdHlwZS5sMiIsIAogICAgICAgICAgICAgICAgICJzaW5nbGVyLmltbXVuZSIsICJzY0FUT01JQ19hbm5vdGF0aW9uIikKCmFubm90YXRpb25fdGFibGVfY2x1c3RlciA8LSBBbGxfc2FtcGxlc19NZXJnZWRAbWV0YS5kYXRhWywgY29sc19uZWVkZWRdICU+JQogIGdyb3VwX2J5KHNldXJhdF9jbHVzdGVycykgJT4lCiAgc3VtbWFyaXNlKAogICAgc2NQcmVkID0gbmFtZXMoc29ydCh0YWJsZShwcmVkaWN0ZWQuaWQpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgQXppbXV0aCA9IG5hbWVzKHNvcnQodGFibGUocHJlZGljdGVkLmNlbGx0eXBlLmwyKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIFNpbmdsZVIgPSBuYW1lcyhzb3J0KHRhYmxlKHNpbmdsZXIuaW1tdW5lKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIHNjQVRPTUlDID0gbmFtZXMoc29ydCh0YWJsZShzY0FUT01JQ19hbm5vdGF0aW9uKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIG5fY2VsbHMgPSBuKCksCiAgICAuZ3JvdXBzID0gImRyb3AiCiAgKSAlPiUKICBhcnJhbmdlKHNldXJhdF9jbHVzdGVycykKCiMgQWRkIGNvbnNlbnN1cyBjb2x1bW4KYW5ub3RhdGlvbl90YWJsZV9jbHVzdGVyIDwtIGFubm90YXRpb25fdGFibGVfY2x1c3RlciAlPiUKICBtdXRhdGUoCiAgICBDb25zZW5zdXMgPSAoc2NQcmVkID09IEF6aW11dGggJiBBemltdXRoID09IFNpbmdsZVIgJiBTaW5nbGVSID09IHNjQVRPTUlDKQogICkKCiMgRGlzcGxheQpjYXQoIlxuPT09IEFOTk9UQVRJT04gQlkgQ0xVU1RFUiA9PT1cblxuIikKa2FibGUoYW5ub3RhdGlvbl90YWJsZV9jbHVzdGVyLCBjYXB0aW9uID0gIkRvbWluYW50IENlbGwgVHlwZSBBbm5vdGF0aW9uIHBlciBDbHVzdGVyIikgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiksIAogICAgICAgICAgICAgICAgZnVsbF93aWR0aCA9IEZBTFNFLCBmb250X3NpemUgPSAxMCkgJT4lCiAgY29sdW1uX3NwZWMoNywgYmFja2dyb3VuZCA9IGlmZWxzZShhbm5vdGF0aW9uX3RhYmxlX2NsdXN0ZXIkQ29uc2Vuc3VzLCAiI2Q0ZWRkYSIsICIjZjhkN2RhIikpCgojIFNhdmUKd3JpdGUuY3N2KGFubm90YXRpb25fdGFibGVfY2x1c3RlciwgIkNlbGxMaW5lc19hbm5vdGF0aW9uX0JZX0NMVVNURVJfMDQtMDItMjAyNi5jc3YiLCAKICAgICAgICAgIHJvdy5uYW1lcyA9IEZBTFNFKQoKY2F0KCJcbuKckyBBbm5vdGF0aW9uIHRhYmxlIGJ5IGNsdXN0ZXIgc2F2ZWRcbiIpCgpgYGAKCgojIyBDcmVhdGUgQW5ub3RhdGlvbiBTdW1tYXJ5IFRhYmxlIC0gQlkgQ0VMTCBMSU5FCmBgYHtyIH0KCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKCiMgRXh0cmFjdCBkb21pbmFudCBsYWJlbCBwZXIgQ0VMTCBMSU5FIHBlciBtZXRob2QKY29sc19uZWVkZWQgPC0gYygiY2VsbF9saW5lIiwgInByZWRpY3RlZC5pZCIsICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiLCAKICAgICAgICAgICAgICAgICAic2luZ2xlci5pbW11bmUiLCAic2NBVE9NSUNfYW5ub3RhdGlvbiIpCgphbm5vdGF0aW9uX3RhYmxlX2NlbGxsaW5lIDwtIEFsbF9zYW1wbGVzX01lcmdlZEBtZXRhLmRhdGFbLCBjb2xzX25lZWRlZF0gJT4lCiAgZ3JvdXBfYnkoY2VsbF9saW5lKSAlPiUKICBzdW1tYXJpc2UoCiAgIHNjUHJlZCA9IG5hbWVzKHNvcnQodGFibGUocHJlZGljdGVkLmlkKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIEF6aW11dGggPSBuYW1lcyhzb3J0KHRhYmxlKHByZWRpY3RlZC5jZWxsdHlwZS5sMiksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICBTaW5nbGVSID0gbmFtZXMoc29ydCh0YWJsZShzaW5nbGVyLmltbXVuZSksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICBzY0FUT01JQyA9IG5hbWVzKHNvcnQodGFibGUoc2NBVE9NSUNfYW5ub3RhdGlvbiksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICBuX2NlbGxzID0gbigpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkgJT4lCiAgYXJyYW5nZShjZWxsX2xpbmUpCgojIEFkZCBjb25zZW5zdXMgY29sdW1uCmFubm90YXRpb25fdGFibGVfY2VsbGxpbmUgPC0gYW5ub3RhdGlvbl90YWJsZV9jZWxsbGluZSAlPiUKICBtdXRhdGUoCiAgICBDb25zZW5zdXMgPSAoc2NQcmVkID09IEF6aW11dGggJiBBemltdXRoID09IFNpbmdsZVIgJiBTaW5nbGVSID09IHNjQVRPTUlDKQogICkKCiMgRGlzcGxheQpjYXQoIlxuPT09IEFOTk9UQVRJT04gQlkgQ0VMTCBMSU5FID09PVxuXG4iKQprYWJsZShhbm5vdGF0aW9uX3RhYmxlX2NlbGxsaW5lLCBjYXB0aW9uID0gIkRvbWluYW50IENlbGwgVHlwZSBBbm5vdGF0aW9uIHBlciBDZWxsIExpbmUiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSwgCiAgICAgICAgICAgICAgICBmdWxsX3dpZHRoID0gRkFMU0UsIGZvbnRfc2l6ZSA9IDEwKSAlPiUKICBjb2x1bW5fc3BlYyg3LCBiYWNrZ3JvdW5kID0gaWZlbHNlKGFubm90YXRpb25fdGFibGVfY2VsbGxpbmUkQ29uc2Vuc3VzLCAiI2Q0ZWRkYSIsICIjZjhkN2RhIikpCgojIFNhdmUKd3JpdGUuY3N2KGFubm90YXRpb25fdGFibGVfY2VsbGxpbmUsICJDZWxsTGluZXNfYW5ub3RhdGlvbl9CWV9DRUxMTElORV8wNC0wMi0yMDI2LmNzdiIsIAogICAgICAgICAgcm93Lm5hbWVzID0gRkFMU0UpCgpjYXQoIlxu4pyTIEFubm90YXRpb24gdGFibGUgYnkgY2VsbCBsaW5lIHNhdmVkXG4iKQoKYGBgCgojIyBDcmVhdGUgQW5ub3RhdGlvbiBTdW1tYXJ5IFRhYmxlIC0gQ29tYmluZWQgVGFibGUgKENsdXN0ZXIgw5cgQ2VsbCBMaW5lKQpgYGB7ciB9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKCiMgRXh0cmFjdCBkb21pbmFudCBsYWJlbCBwZXIgQ0xVU1RFUiDDlyBDRUxMIExJTkUgw5cgTUVUSE9ECmNvbHNfbmVlZGVkIDwtIGMoInNldXJhdF9jbHVzdGVycyIsICJjZWxsX2xpbmUiLCAicHJlZGljdGVkLmlkIiwgInByZWRpY3RlZC5jZWxsdHlwZS5sMiIsIAogICAgICAgICAgICAgICAgICJzaW5nbGVyLmltbXVuZSIsICJzY0FUT01JQ19hbm5vdGF0aW9uIikKCmFubm90YXRpb25fdGFibGVfY29tYmluZWQgPC0gQWxsX3NhbXBsZXNfTWVyZ2VkQG1ldGEuZGF0YVssIGNvbHNfbmVlZGVkXSAlPiUKICBncm91cF9ieShzZXVyYXRfY2x1c3RlcnMsIGNlbGxfbGluZSkgJT4lCiAgc3VtbWFyaXNlKAogICBzY1ByZWQgPSBuYW1lcyhzb3J0KHRhYmxlKHByZWRpY3RlZC5pZCksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICBBemltdXRoID0gbmFtZXMoc29ydCh0YWJsZShwcmVkaWN0ZWQuY2VsbHR5cGUubDIpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgU2luZ2xlUiA9IG5hbWVzKHNvcnQodGFibGUoc2luZ2xlci5pbW11bmUpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgc2NBVE9NSUMgPSBuYW1lcyhzb3J0KHRhYmxlKHNjQVRPTUlDX2Fubm90YXRpb24pLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgbl9jZWxscyA9IG4oKSwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApICU+JQogIGFycmFuZ2UoY2VsbF9saW5lLCBzZXVyYXRfY2x1c3RlcnMpCgojIEFkZCBjb25zZW5zdXMgY29sdW1uCmFubm90YXRpb25fdGFibGVfY29tYmluZWQgPC0gYW5ub3RhdGlvbl90YWJsZV9jb21iaW5lZCAlPiUKICBtdXRhdGUoCiAgICBDb25zZW5zdXMgPSAoc2NQcmVkID09IEF6aW11dGggJiBBemltdXRoID09IFNpbmdsZVIgJiBTaW5nbGVSID09IHNjQVRPTUlDKQogICkKCiMgRGlzcGxheSAoZmlyc3QgMjAgcm93cykKY2F0KCJcbj09PSBBTk5PVEFUSU9OIEJZIENMVVNURVIgw5cgQ0VMTCBMSU5FIChGaXJzdCAyMCByb3dzKSA9PT1cblxuIikKa2FibGUoaGVhZChhbm5vdGF0aW9uX3RhYmxlX2NvbWJpbmVkLCAyMCksIAogICAgICBjYXB0aW9uID0gIkRvbWluYW50IEFubm90YXRpb24gcGVyIENsdXN0ZXIgw5cgQ2VsbCBMaW5lIikgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiksIAogICAgICAgICAgICAgICAgZnVsbF93aWR0aCA9IEZBTFNFLCBmb250X3NpemUgPSA5KSAlPiUKICBjb2x1bW5fc3BlYyg4LCBiYWNrZ3JvdW5kID0gaWZlbHNlKGhlYWQoYW5ub3RhdGlvbl90YWJsZV9jb21iaW5lZCRDb25zZW5zdXMsIDIwKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiNkNGVkZGEiLCAiI2Y4ZDdkYSIpKQoKIyBTYXZlCndyaXRlLmNzdihhbm5vdGF0aW9uX3RhYmxlX2NvbWJpbmVkLCAKICAgICAgICAgICJDZWxsTGluZXNfYW5ub3RhdGlvbl9CWV9DTFVTVEVSX0FORF9DRUxMTElORV8wNC0wMi0yMDI2LmNzdiIsIAogICAgICAgICAgcm93Lm5hbWVzID0gRkFMU0UpCgpjYXQoIlxu4pyTIENvbWJpbmVkIGFubm90YXRpb24gdGFibGUgKGNsdXN0ZXIgw5cgY2VsbCBsaW5lKSBzYXZlZFxuIikKY2F0KCJUb3RhbCByb3dzOiIsIG5yb3coYW5ub3RhdGlvbl90YWJsZV9jb21iaW5lZCksICJcbiIpCgpgYGAKIyBDcmVhdGUgQW5ub3RhdGlvbiBTdW1tYXJ5CmBgYHtyIH0KbGlicmFyeShwdXJycikgICMgTWFrZSBzdXJlIHRoaXMgaXMgbG9hZGVkCgojIERlZmluZSBhbm5vdGF0aW9uIG1ldGhvZHMKbWV0aG9kcyA8LSBjKAogICJwcmVkaWN0ZWQuaWQiLCAgICAgICAgICAgICAjIHNjUHJlZAogICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiLCAgICAjIEF6aW11dGggKGwyIHByZWRpY3Rpb24pCiAgInNpbmdsZXIuaW1tdW5lIiwgICAgICAgICAgICMgU2luZ2xlUgogICJzY0FUT01JQ19hbm5vdGF0aW9uIiAgICAgICAjIHNjQVRPTUlDCikKCiMgQ3JlYXRlIHN1bW1hcnkgLSBtb3N0IGNvbW1vbiBsYWJlbCBwZXIgY2x1c3RlciBmb3IgZWFjaCBtZXRob2QKYW5ub3RhdGlvbl9zdW1tYXJ5IDwtIG1hcF9kZnIobWV0aG9kcywgZnVuY3Rpb24obSkgewogIGRmIDwtIEFsbF9zYW1wbGVzX01lcmdlZEBtZXRhLmRhdGEKICBkZiAlPiUKICAgIGZpbHRlcighaXMubmEoLmRhdGFbW21dXSkpICU+JQogICAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzLCBsYWJlbCA9IC5kYXRhW1ttXV0pICU+JQogICAgc3VtbWFyaXNlKG4gPSBuKCksIC5ncm91cHMgPSAiZHJvcCIpICU+JQogICAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzKSAlPiUKICAgIHNsaWNlX21heChuLCBuID0gMSwgd2l0aF90aWVzID0gRkFMU0UpICU+JQogICAgbXV0YXRlKG1ldGhvZCA9IG0pCn0pCgojIFJlbmFtZSBtZXRob2RzIGZvciBkaXNwbGF5CmFubm90YXRpb25fc3VtbWFyeSA8LSBhbm5vdGF0aW9uX3N1bW1hcnkgJT4lCiAgbXV0YXRlKG1ldGhvZCA9IHJlY29kZShtZXRob2QsCiAgICAgICAgICAgICAgICAgICAgICAgICAicHJlZGljdGVkLmlkIiA9ICJzY1ByZWQiLAogICAgICAgICAgICAgICAgICAgICAgICAgInByZWRpY3RlZC5jZWxsdHlwZS5sMiIgPSAiQXppbXV0aC5sMiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAic2luZ2xlci5pbW11bmUiID0gIlNpbmdsZVIoSW1tdW5lKSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAic2NBVE9NSUNfYW5ub3RhdGlvbiIgPSAic2NBVE9NSUMiKSkKCiMgU2V0IG1ldGhvZCBvcmRlcgphbm5vdGF0aW9uX3N1bW1hcnkkbWV0aG9kIDwtIGZhY3Rvcihhbm5vdGF0aW9uX3N1bW1hcnkkbWV0aG9kLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygic2NQcmVkIiwgIkF6aW11dGgubDIiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTaW5nbGVSKEltbXVuZSkiLCAic2NBVE9NSUMiKSkKCiMgRW5zdXJlIGNsdXN0ZXIgb3JkZXIKYW5ub3RhdGlvbl9zdW1tYXJ5JHNldXJhdF9jbHVzdGVycyA8LSBmYWN0b3IoYW5ub3RhdGlvbl9zdW1tYXJ5JHNldXJhdF9jbHVzdGVycywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGFzLmNoYXJhY3RlcigwOjEzKSkgICMgQWRqdXN0IHJhbmdlCgoKYGBgCgojIyBTdW1tYXJ5IFN0YXRpc3RpY3MKYGBge3IgfQojIENlbGwgY291bnRzIHBlciBtZXRob2QKY2F0KCJcbj09PSBDZWxscyBBbm5vdGF0ZWQgUGVyIE1ldGhvZCA9PT1cblxuIikKbWV0aG9kX2NvbHMgPC0gYygicHJlZGljdGVkLmlkIiwgInByZWRpY3RlZC5jZWxsdHlwZS5sMiIsICJzaW5nbGVyLmltbXVuZSIsICJzY0FUT01JQ19hbm5vdGF0aW9uIikKZm9yKG0gaW4gbWV0aG9kX2NvbHMpewogIG5fYW5ub3RhdGVkIDwtIHN1bSghaXMubmEoQWxsX3NhbXBsZXNfTWVyZ2VkQG1ldGEuZGF0YVtbbV1dKSkKICBjYXQoc3ByaW50ZigiJS0yNXM6ICU1ZCBjZWxscyAoJS4xZiUlKVxuIiwgCiAgICAgICAgICAgICAgbSwgbl9hbm5vdGF0ZWQsIG5fYW5ub3RhdGVkL25jb2woQWxsX3NhbXBsZXNfTWVyZ2VkKSoxMDApKQp9CgojIE51bWJlciBvZiB1bmlxdWUgbGFiZWxzIHBlciBtZXRob2QKY2F0KCJcbj09PSBVbmlxdWUgTGFiZWxzIFBlciBNZXRob2QgPT09XG5cbiIpCmFubm90YXRpb25fc3VtbWFyeSAlPiUKICBncm91cF9ieShtZXRob2QpICU+JQogIHN1bW1hcmlzZShuX3VuaXF1ZV9sYWJlbHMgPSBuX2Rpc3RpbmN0KGxhYmVsKSkgJT4lCiAgcHJpbnQoKQoKYGBgCiMgQmFzaWMgSGVhdG1hcCBWaXN1YWxpemF0aW9uCmBgYHtyLCBmaWcuaGVpZ2h0PSA1LCBmaWcud2lkdGg9IDE4fQpsaWJyYXJ5KGdncGxvdDIpCgpnZ3Bsb3QoYW5ub3RhdGlvbl9zdW1tYXJ5LCBhZXMoeCA9IHNldXJhdF9jbHVzdGVycywgeSA9IG1ldGhvZCwgZmlsbCA9IGxhYmVsKSkgKwogIGdlb21fdGlsZShjb2xvciA9ICJ3aGl0ZSIsIGxpbmV3aWR0aCA9IDAuNSkgKwogIHNjYWxlX2ZpbGxfZGlzY3JldGUoKSArCiAgbGFicygKICAgIHggPSAiU2V1cmF0IENsdXN0ZXJzIiwKICAgIHkgPSAiQW5ub3RhdGlvbiBNZXRob2QiLAogICAgZmlsbCA9ICJBc3NpZ25lZCBDZWxsIFR5cGUiLAogICAgdGl0bGUgPSAiQ3Jvc3MtTWV0aG9kIENvbXBhcmlzb24gb2YgQ2VsbCBUeXBlIEFubm90YXRpb25zIgogICkgKwogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTQpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE2KQogICkKYGBgCgojIyBFbmhhbmNlZCBIZWF0bWFwIHdpdGggQ3VzdG9tIENvbG9ycwpgYGB7ciwgZmlnLmhlaWdodD0gNSwgZmlnLndpZHRoPSAxOCB9CmxpYnJhcnkoUkNvbG9yQnJld2VyKQoKIyBHZW5lcmF0ZSBjb2xvciBwYWxldHRlCm51bV9jb2xvcnMgPC0gbGVuZ3RoKHVuaXF1ZShhbm5vdGF0aW9uX3N1bW1hcnkkbGFiZWwpKQpteV9jb2xvcnMgPC0gY29sb3JSYW1wUGFsZXR0ZShicmV3ZXIucGFsKDgsICJTZXQyIikpKG51bV9jb2xvcnMpCgpwX2hlYXRtYXAgPC0gZ2dwbG90KGFubm90YXRpb25fc3VtbWFyeSwgYWVzKHggPSBzZXVyYXRfY2x1c3RlcnMsIHkgPSBtZXRob2QsIGZpbGwgPSBsYWJlbCkpICsKICBnZW9tX3RpbGUoY29sb3IgPSAid2hpdGUiLCBsaW5ld2lkdGggPSAwLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBteV9jb2xvcnMpICsKICBzY2FsZV94X2Rpc2NyZXRlKGRyb3AgPSBGQUxTRSkgKyAgIyBLZWVwIGFsbCBjbHVzdGVyIGxldmVscwogIGxhYnMoCiAgICB4ID0gIlNldXJhdCBDbHVzdGVycyIsCiAgICB5ID0gIkFubm90YXRpb24gTWV0aG9kIiwKICAgIGZpbGwgPSAiQXNzaWduZWQgQ2VsbCBUeXBlIiwKICAgIHRpdGxlID0gIkNyb3NzLU1ldGhvZCBDb21wYXJpc29uIG9mIENlbGwgVHlwZSBBbm5vdGF0aW9ucyIKICApICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNiksCiAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiCiAgKQoKcHJpbnQocF9oZWF0bWFwKQoKIyBTYXZlIGhpZ2gtcXVhbGl0eSB2ZXJzaW9ucwpnZ3NhdmUoIkNlbGxMaW5lc19hbm5vdGF0aW9uX2hlYXRtYXBfMDQtMDItMjAyNi5wbmciLCBwbG90ID0gcF9oZWF0bWFwLCAKICAgICAgIHdpZHRoID0gMTQsIGhlaWdodCA9IDUsIGRwaSA9IDMwMCwgYmcgPSAid2hpdGUiKQpnZ3NhdmUoIkNlbGxMaW5lc19hbm5vdGF0aW9uX2hlYXRtYXBfMDQtMDItMjAyNi5wZGYiLCBwbG90ID0gcF9oZWF0bWFwLCAKICAgICAgIHdpZHRoID0gMTQsIGhlaWdodCA9IDUpCgpjYXQoIlxu4pyTIEhlYXRtYXAgc2F2ZWQgYXMgUE5HIGFuZCBQREZcbiIpCgoKYGBgCiMgIE1BTlVTQ1JJUFQgRklHVVJFCiMjICBNQU5VU0NSSVBUIEZJR1VSRTogVGV4dC1CYXNlZCBIZWF0bWFwIEJZIENMVVNURVJTCmBgYHtyLCBmaWcuaGVpZ2h0PSA1LCBmaWcud2lkdGg9IDMwIH0KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQoKIyBQcmVwYXJlIGRhdGEgYnkgQ0xVU1RFUlMKY29sc19uZWVkZWQgPC0gYygic2V1cmF0X2NsdXN0ZXJzIiwgInByZWRpY3RlZC5pZCIsICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiLCAKICAgICAgICAgICAgICAgICAic2luZ2xlci5pbW11bmUiLCAic2NBVE9NSUNfYW5ub3RhdGlvbiIpCgpoZWF0bWFwX2RhdGFfY2x1c3RlcnMgPC0gQWxsX3NhbXBsZXNfTWVyZ2VkQG1ldGEuZGF0YVssIGNvbHNfbmVlZGVkXSAlPiUKICBncm91cF9ieShzZXVyYXRfY2x1c3RlcnMpICU+JQogIHN1bW1hcmlzZSgKICAgc2NQcmVkID0gbmFtZXMoc29ydCh0YWJsZShwcmVkaWN0ZWQuaWQpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgQXppbXV0aCA9IG5hbWVzKHNvcnQodGFibGUocHJlZGljdGVkLmNlbGx0eXBlLmwyKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIFNpbmdsZVIgPSBuYW1lcyhzb3J0KHRhYmxlKHNpbmdsZXIuaW1tdW5lKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIHNjQVRPTUlDID0gbmFtZXMoc29ydCh0YWJsZShzY0FUT01JQ19hbm5vdGF0aW9uKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhzY1ByZWQsIEF6aW11dGgsIFNpbmdsZVIsIHNjQVRPTUlDKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiTWV0aG9kIiwgdmFsdWVzX3RvID0gIkNlbGxUeXBlIikgJT4lCiAgbXV0YXRlKAogICAgTWV0aG9kID0gZmFjdG9yKE1ldGhvZCwgbGV2ZWxzID0gYygic2NBVE9NSUMiLCAiU2luZ2xlUiIsICJBemltdXRoIiwgInNjUHJlZCIpKSwKICAgIHNldXJhdF9jbHVzdGVycyA9IGZhY3RvcihzZXVyYXRfY2x1c3RlcnMsIGxldmVscyA9IGFzLmNoYXJhY3RlcigwOjEzKSksICAjIENIQU5HRSBSQU5HRQogICAgQ2VsbFR5cGVfZGlzcGxheSA9IGlmZWxzZShuY2hhcihDZWxsVHlwZSkgPiAyMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMChzdWJzdHIoQ2VsbFR5cGUsIDEsIDE3KSwgIi4uLiIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2VsbFR5cGUpCiAgKQoKIyBDcmVhdGUgbWFudXNjcmlwdCBmaWd1cmUgQlkgQ0xVU1RFUlMKcF9tYW51c2NyaXB0X2NsdXN0ZXJzIDwtIGdncGxvdChoZWF0bWFwX2RhdGFfY2x1c3RlcnMsIAogICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gc2V1cmF0X2NsdXN0ZXJzLCB5ID0gTWV0aG9kLCBsYWJlbCA9IENlbGxUeXBlX2Rpc3BsYXkpKSArCiAgZ2VvbV90aWxlKGNvbG9yID0gImdyZXkyMCIsIGZpbGwgPSAid2hpdGUiLCBsaW5ld2lkdGggPSAwLjgpICsKICBnZW9tX3RleHQoc2l6ZSA9IDMsIGhqdXN0ID0gMC41LCB2anVzdCA9IDAuNSwgZm9udGZhY2UgPSAiYm9sZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRGlzY29yZGFuY2UgaW4gQXV0b21hdGVkIENlbGwgVHlwZSBBbm5vdGF0aW9uIC0gQ2VsbCBMaW5lcyBieSBDbHVzdGVycyIsCiAgICB4ID0gIlNldXJhdCBDbHVzdGVyIiwKICAgIHkgPSAiQW5ub3RhdGlvbiBNZXRob2QiCiAgKSArCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE0KSwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNCksCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiwgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiLCBzaXplID0gMTgpLAogICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKDEwLCAxMCwgMTAsIDEwKQogICkKCnByaW50KHBfbWFudXNjcmlwdF9jbHVzdGVycykKCiMgU2F2ZSBoaWdoLXF1YWxpdHkgdmVyc2lvbnMKZ2dzYXZlKCJGaWd1cmVYX0NlbGxMaW5lc19Bbm5vdGF0aW9uX0JZX0NMVVNURVJTLnBuZyIsIAogICAgICAgcGxvdCA9IHBfbWFudXNjcmlwdF9jbHVzdGVycywgd2lkdGggPSAzMCwgaGVpZ2h0ID0gNSwgZHBpID0gNjAwLCBiZyA9ICJ3aGl0ZSIpCmdnc2F2ZSgiRmlndXJlWF9DZWxsTGluZXNfQW5ub3RhdGlvbl9CWV9DTFVTVEVSUy5wZGYiLCAKICAgICAgIHBsb3QgPSBwX21hbnVzY3JpcHRfY2x1c3RlcnMsIHdpZHRoID0gMzAsIGhlaWdodCA9IDUpCmdnc2F2ZSgiRmlndXJlWF9DZWxsTGluZXNfQW5ub3RhdGlvbl9CWV9DTFVTVEVSUy50aWZmIiwgCiAgICAgICBwbG90ID0gcF9tYW51c2NyaXB0X2NsdXN0ZXJzLCB3aWR0aCA9IDMwLCBoZWlnaHQgPSA1LCBkcGkgPSA2MDAsIGJnID0gIndoaXRlIikKCmNhdCgiXG7inJMgTWFudXNjcmlwdCBmaWd1cmUgQlkgQ0xVU1RFUlMgc2F2ZWQgKFBORywgUERGLCBUSUZGIGF0IDYwMCBEUEkpXG4iKQoKYGBgCgojIyAgTUFOVVNDUklQVCBGSUdVUkU6IFRleHQtQmFzZWQgSGVhdG1hcCBCWSBDRUxMIExJTkVTCmBgYHtyLCBmaWcuaGVpZ2h0PSA1LCBmaWcud2lkdGg9IDE4IH0KIyBQcmVwYXJlIGRhdGEgYnkgQ0VMTCBMSU5FUwpjb2xzX25lZWRlZF9jZWxsbGluZSA8LSBjKCJjZWxsX2xpbmUiLCAicHJlZGljdGVkLmlkIiwgInByZWRpY3RlZC5jZWxsdHlwZS5sMiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICJzaW5nbGVyLmltbXVuZSIsICJzY0FUT01JQ19hbm5vdGF0aW9uIikKCmhlYXRtYXBfZGF0YV9jZWxsbGluZXMgPC0gQWxsX3NhbXBsZXNfTWVyZ2VkQG1ldGEuZGF0YVssIGNvbHNfbmVlZGVkX2NlbGxsaW5lXSAlPiUKICBncm91cF9ieShjZWxsX2xpbmUpICU+JQogIHN1bW1hcmlzZSgKICAgc2NQcmVkID0gbmFtZXMoc29ydCh0YWJsZShwcmVkaWN0ZWQuaWQpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgQXppbXV0aCA9IG5hbWVzKHNvcnQodGFibGUocHJlZGljdGVkLmNlbGx0eXBlLmwyKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIFNpbmdsZVIgPSBuYW1lcyhzb3J0KHRhYmxlKHNpbmdsZXIuaW1tdW5lKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIHNjQVRPTUlDID0gbmFtZXMoc29ydCh0YWJsZShzY0FUT01JQ19hbm5vdGF0aW9uKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhzY1ByZWQsIEF6aW11dGgsIFNpbmdsZVIsIHNjQVRPTUlDKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiTWV0aG9kIiwgdmFsdWVzX3RvID0gIkNlbGxUeXBlIikgJT4lCiAgbXV0YXRlKAogICAgTWV0aG9kID0gZmFjdG9yKE1ldGhvZCwgbGV2ZWxzID0gYygic2NBVE9NSUMiLCAiU2luZ2xlUiIsICJBemltdXRoIiwgInNjUHJlZCIpKSwKICAgIENlbGxUeXBlX2Rpc3BsYXkgPSBpZmVsc2UobmNoYXIoQ2VsbFR5cGUpID4gMjAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAoc3Vic3RyKENlbGxUeXBlLCAxLCAxNyksICIuLi4iKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENlbGxUeXBlKQogICkKCiMgQ3JlYXRlIG1hbnVzY3JpcHQgZmlndXJlIEJZIENFTEwgTElORVMKcF9tYW51c2NyaXB0X2NlbGxsaW5lcyA8LSBnZ3Bsb3QoaGVhdG1hcF9kYXRhX2NlbGxsaW5lcywgCiAgICAgICAgICAgICAgICAgICAgICAgYWVzKHggPSBjZWxsX2xpbmUsIHkgPSBNZXRob2QsIGxhYmVsID0gQ2VsbFR5cGVfZGlzcGxheSkpICsKICBnZW9tX3RpbGUoY29sb3IgPSAiZ3JleTIwIiwgZmlsbCA9ICJ3aGl0ZSIsIGxpbmV3aWR0aCA9IDAuOCkgKwogIGdlb21fdGV4dChzaXplID0gMywgaGp1c3QgPSAwLjUsIHZqdXN0ID0gMC41LCBmb250ZmFjZSA9ICJib2xkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJEaXNjb3JkYW5jZSBpbiBBdXRvbWF0ZWQgQ2VsbCBUeXBlIEFubm90YXRpb24gLSBCeSBDZWxsIExpbmUiLAogICAgeCA9ICJDZWxsIExpbmUiLAogICAgeSA9ICJBbm5vdGF0aW9uIE1ldGhvZCIKICApICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE2KSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBzaXplID0gMTQsIGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBzaXplID0gMTQpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYsIGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE4KSwKICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigxMCwgMTAsIDEwLCAxMCkKICApCgpwcmludChwX21hbnVzY3JpcHRfY2VsbGxpbmVzKQoKIyBTYXZlIGhpZ2gtcXVhbGl0eSB2ZXJzaW9ucwpnZ3NhdmUoIkZpZ3VyZVhfQ2VsbExpbmVzX0Fubm90YXRpb25fQllfQ0VMTExJTkUucG5nIiwgCiAgICAgICBwbG90ID0gcF9tYW51c2NyaXB0X2NlbGxsaW5lcywgd2lkdGggPSAxNCwgaGVpZ2h0ID0gNSwgZHBpID0gNjAwLCBiZyA9ICJ3aGl0ZSIpCmdnc2F2ZSgiRmlndXJlWF9DZWxsTGluZXNfQW5ub3RhdGlvbl9CWV9DRUxMTElORS5wZGYiLCAKICAgICAgIHBsb3QgPSBwX21hbnVzY3JpcHRfY2VsbGxpbmVzLCB3aWR0aCA9IDE0LCBoZWlnaHQgPSA1KQpnZ3NhdmUoIkZpZ3VyZVhfQ2VsbExpbmVzX0Fubm90YXRpb25fQllfQ0VMTExJTkUudGlmZiIsIAogICAgICAgcGxvdCA9IHBfbWFudXNjcmlwdF9jZWxsbGluZXMsIHdpZHRoID0gMTQsIGhlaWdodCA9IDUsIGRwaSA9IDYwMCwgYmcgPSAid2hpdGUiKQoKY2F0KCJcbuKckyBNYW51c2NyaXB0IGZpZ3VyZSBCWSBDRUxMIExJTkUgc2F2ZWQgKFBORywgUERGLCBUSUZGIGF0IDYwMCBEUEkpXG4iKQoKYGBgCiMjICBDT01CSU5FRCBNQU5VU0NSSVBUIEZJR1VSRTogQ2x1c3RlcnMgw5cgQ2VsbCBMaW5lcyAoT3B0aW9uYWwpCmBgYHtyLCBmaWcuaGVpZ2h0PSA1LCBmaWcud2lkdGg9IDQ4IH0KIyBQcmVwYXJlIGRhdGEgYnkgQ0xVU1RFUiDDlyBDRUxMIExJTkUKY29sc19uZWVkZWRfY29tYmluZWQgPC0gYygic2V1cmF0X2NsdXN0ZXJzIiwgImNlbGxfbGluZSIsICJwcmVkaWN0ZWQuaWQiLCAicHJlZGljdGVkLmNlbGx0eXBlLmwyIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgInNpbmdsZXIuaW1tdW5lIiwgInNjQVRPTUlDX2Fubm90YXRpb24iKQoKaGVhdG1hcF9kYXRhX2NvbWJpbmVkIDwtIEFsbF9zYW1wbGVzX01lcmdlZEBtZXRhLmRhdGFbLCBjb2xzX25lZWRlZF9jb21iaW5lZF0gJT4lCiAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzLCBjZWxsX2xpbmUpICU+JQogIHN1bW1hcmlzZSgKICAgc2NQcmVkID0gbmFtZXMoc29ydCh0YWJsZShwcmVkaWN0ZWQuaWQpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgQXppbXV0aCA9IG5hbWVzKHNvcnQodGFibGUocHJlZGljdGVkLmNlbGx0eXBlLmwyKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIFNpbmdsZVIgPSBuYW1lcyhzb3J0KHRhYmxlKHNpbmdsZXIuaW1tdW5lKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIHNjQVRPTUlDID0gbmFtZXMoc29ydCh0YWJsZShzY0FUT01JQ19hbm5vdGF0aW9uKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhzY1ByZWQsIEF6aW11dGgsIFNpbmdsZVIsIHNjQVRPTUlDKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiTWV0aG9kIiwgdmFsdWVzX3RvID0gIkNlbGxUeXBlIikgJT4lCiAgbXV0YXRlKAogICAgTWV0aG9kID0gZmFjdG9yKE1ldGhvZCwgbGV2ZWxzID0gYygic2NBVE9NSUMiLCAiU2luZ2xlUiIsICJBemltdXRoIiwgInNjUHJlZCIpKSwKICAgIHNldXJhdF9jbHVzdGVycyA9IGZhY3RvcihzZXVyYXRfY2x1c3RlcnMsIGxldmVscyA9IGFzLmNoYXJhY3RlcigwOjEzKSksCiAgICBjb21iaW5lZF9sYWJlbCA9IHBhc3RlMCgiQyIsIHNldXJhdF9jbHVzdGVycywgIl8iLCBjZWxsX2xpbmUpLAogICAgQ2VsbFR5cGVfZGlzcGxheSA9IGlmZWxzZShuY2hhcihDZWxsVHlwZSkgPiAxNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMChzdWJzdHIoQ2VsbFR5cGUsIDEsIDEyKSwgIi4uLiIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2VsbFR5cGUpCiAgKQoKIyBDcmVhdGUgY29tYmluZWQgZmlndXJlCnBfbWFudXNjcmlwdF9jb21iaW5lZCA8LSBnZ3Bsb3QoaGVhdG1hcF9kYXRhX2NvbWJpbmVkLCAKICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IGNvbWJpbmVkX2xhYmVsLCB5ID0gTWV0aG9kLCBsYWJlbCA9IENlbGxUeXBlX2Rpc3BsYXkpKSArCiAgZ2VvbV90aWxlKGNvbG9yID0gImdyZXkyMCIsIGZpbGwgPSAid2hpdGUiLCBsaW5ld2lkdGggPSAwLjUpICsKICBnZW9tX3RleHQoc2l6ZSA9IDIsIGhqdXN0ID0gMC41LCB2anVzdCA9IDAuNSwgZm9udGZhY2UgPSAiYm9sZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQ2VsbCBUeXBlIEFubm90YXRpb24gLSBCeSBDbHVzdGVyIMOXIENlbGwgTGluZSIsCiAgICB4ID0gIkNsdXN0ZXJfQ2VsbExpbmUiLAogICAgeSA9ICJBbm5vdGF0aW9uIE1ldGhvZCIKICApICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBzaXplID0gOCwgYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCB2anVzdCA9IDAuNSksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBzaXplID0gMTIpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQsIGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE2KSwKICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigxMCwgMTAsIDEwLCAxMCkKICApCgpwcmludChwX21hbnVzY3JpcHRfY29tYmluZWQpCgojIFNhdmUKZ2dzYXZlKCJGaWd1cmVYX0NlbGxMaW5lc19Bbm5vdGF0aW9uX0NMVVNURVJTX3hfQ0VMTExJTkVTLnBuZyIsIAogICAgICAgcGxvdCA9IHBfbWFudXNjcmlwdF9jb21iaW5lZCwgd2lkdGggPSAyNCwgaGVpZ2h0ID0gNiwgZHBpID0gNjAwLCBiZyA9ICJ3aGl0ZSIpCmdnc2F2ZSgiRmlndXJlWF9DZWxsTGluZXNfQW5ub3RhdGlvbl9DTFVTVEVSU194X0NFTExMSU5FUy5wZGYiLCAKICAgICAgIHBsb3QgPSBwX21hbnVzY3JpcHRfY29tYmluZWQsIHdpZHRoID0gMjQsIGhlaWdodCA9IDYpCgpjYXQoIlxu4pyTIENvbWJpbmVkIG1hbnVzY3JpcHQgZmlndXJlIChDbHVzdGVyIMOXIENlbGwgTGluZSkgc2F2ZWRcbiIpCgpgYGAKCgoKCgoKCiMgc2Vzc2lvbkluZm8oKQpgYGB7cn0KCnNlc3Npb25JbmZvKCkKCgpgYGAKCgo=