1 load libraries

2 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 

3 Create Annotation Summary Table

3.1 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

3.2 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

3.3 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 

4 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

4.1 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

5 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)
  )

5.1 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

6 MANUSCRIPT FIGURE

6.1 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)

6.2 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)

6.3 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

7 sessionInfo()


sessionInfo()
R version 4.5.2 (2025-10-31)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.3 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.12.0 
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0  LAPACK version 3.12.0

locale:
 [1] LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C               LC_TIME=fr_FR.UTF-8        LC_COLLATE=en_GB.UTF-8    
 [5] LC_MONETARY=fr_FR.UTF-8    LC_MESSAGES=en_GB.UTF-8    LC_PAPER=fr_FR.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=fr_FR.UTF-8 LC_IDENTIFICATION=C       

time zone: Europe/Paris
tzcode source: system (glibc)

attached base packages:
[1] stats4    stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] tidyr_1.3.2                 RColorBrewer_1.1-3          purrr_1.2.1                 kableExtra_1.4.0           
 [5] knitr_1.51                  Azimuth_0.5.0               shinyBS_0.63.0              pbmcsca.SeuratData_3.0.0   
 [9] pbmcref.SeuratData_1.0.0    SeuratData_0.2.2.9002       SeuratDisk_0.0.0.9021       presto_1.0.0               
[13] data.table_1.18.2.1         Rcpp_1.1.1                  remotes_2.5.0               SingleR_2.12.0             
[17] celldex_1.20.0              SummarizedExperiment_1.40.0 Biobase_2.70.0              GenomicRanges_1.62.1       
[21] Seqinfo_1.0.0               IRanges_2.44.0              S4Vectors_0.48.0            BiocGenerics_0.56.0        
[25] generics_0.1.4              MatrixGenerics_1.22.0       matrixStats_1.5.0           scPred_1.9.2               
[29] pheatmap_1.0.13             ggplot2_4.0.1               patchwork_1.3.2             dplyr_1.1.4                
[33] Seurat_5.4.0                SeuratObject_5.3.0          sp_2.2-0                   

loaded via a namespace (and not attached):
  [1] dichromat_2.0-0.1                 nnet_7.3-20                       goftest_1.2-3                    
  [4] DT_0.34.0                         Biostrings_2.78.0                 HDF5Array_1.38.0                 
  [7] vctrs_0.7.1                       spatstat.random_3.4-4             digest_0.6.39                    
 [10] png_0.1-8                         gypsum_1.6.0                      ggrepel_0.9.6                    
 [13] deldir_2.0-4                      parallelly_1.46.1                 MASS_7.3-65                      
 [16] Signac_1.16.0                     reshape2_1.4.5                    httpuv_1.6.16                    
 [19] foreach_1.5.2                     withr_3.0.2                       xfun_0.56                        
 [22] survival_3.8-3                    EnsDb.Hsapiens.v86_2.99.0         memoise_2.0.1                    
 [25] ggbeeswarm_0.7.3                  systemfonts_1.3.1                 ragg_1.5.0                       
 [28] zoo_1.8-15                        gtools_3.9.5                      pbapply_1.7-4                    
 [31] KEGGREST_1.50.0                   promises_1.5.0                    otel_0.2.0                       
 [34] httr_1.4.7                        restfulr_0.0.16                   globals_0.18.0                   
 [37] fitdistrplus_1.2-6                rhdf5filters_1.22.0               rhdf5_2.54.1                     
 [40] rstudioapi_0.18.0                 UCSC.utils_1.6.1                  miniUI_0.1.2                     
 [43] curl_7.0.0                        h5mread_1.2.1                     polyclip_1.10-7                  
 [46] ExperimentHub_3.0.0               SparseArray_1.10.8                xtable_1.8-4                     
 [49] stringr_1.6.0                     evaluate_1.0.5                    S4Arrays_1.10.1                  
 [52] BiocFileCache_3.0.0               irlba_2.3.5.1                     filelock_1.0.3                   
 [55] hdf5r_1.3.12                      ROCR_1.0-12                       harmony_1.2.4                    
 [58] reticulate_1.44.1                 spatstat.data_3.1-9               magrittr_2.0.4                   
 [61] lmtest_0.9-40                     later_1.4.5                       lattice_0.22-7                   
 [64] spatstat.geom_3.7-0               future.apply_1.20.1               scattermore_1.2                  
 [67] XML_3.99-0.20                     cowplot_1.2.0                     RcppAnnoy_0.0.23                 
 [70] class_7.3-23                      pillar_1.11.1                     nlme_3.1-168                     
 [73] iterators_1.0.14                  pwalign_1.6.0                     caTools_1.18.3                   
 [76] compiler_4.5.2                    beachmat_2.26.0                   RSpectra_0.16-2                  
 [79] stringi_1.8.7                     gower_1.0.2                       tensor_1.5.1                     
 [82] lubridate_1.9.4                   GenomicAlignments_1.46.0          plyr_1.8.9                       
 [85] crayon_1.5.3                      abind_1.4-8                       BiocIO_1.20.0                    
 [88] googledrive_2.1.2                 bit_4.6.0                         fastmatch_1.1-8                  
 [91] textshaping_1.0.4                 codetools_0.2-20                  recipes_1.3.1                    
 [94] bslib_0.10.0                      alabaster.ranges_1.10.0           plotly_4.12.0                    
 [97] mime_0.13                         splines_4.5.2                     fastDummies_1.7.5                
[100] dbplyr_2.5.1                      sparseMatrixStats_1.22.0          cellranger_1.1.0                 
[103] utf8_1.2.6                        blob_1.3.0                        BiocVersion_3.22.0               
[106] seqLogo_1.76.0                    AnnotationFilter_1.34.0           fs_1.6.6                         
[109] listenv_0.10.0                    DelayedMatrixStats_1.32.0         tibble_3.3.1                     
[112] Matrix_1.7-4                      svglite_2.2.2                     pkgconfig_2.0.3                  
[115] tools_4.5.2                       cachem_1.1.0                      cigarillo_1.0.0                  
[118] RSQLite_2.4.5                     viridisLite_0.4.2                 DBI_1.2.3                        
[121] fastmap_1.2.0                     rmarkdown_2.30                    scales_1.4.0                     
[124] grid_4.5.2                        ica_1.0-3                         shinydashboard_0.7.3             
[127] Rsamtools_2.26.0                  sass_0.4.10                       AnnotationHub_4.0.0              
[130] BiocManager_1.30.27               dotCall64_1.2                     RANN_2.6.2                       
[133] alabaster.schemas_1.10.0          rpart_4.1.24                      farver_2.1.2                     
[136] yaml_2.3.12                       rtracklayer_1.70.1                cli_3.6.5                        
[139] lifecycle_1.0.5                   caret_7.0-1                       rsconnect_1.7.0                  
[142] uwot_0.2.4                        lava_1.8.2                        BSgenome.Hsapiens.UCSC.hg38_1.4.5
[145] BiocParallel_1.44.0               timechange_0.3.0                  gtable_0.3.6                     
[148] rjson_0.2.23                      ggridges_0.5.7                    progressr_0.18.0                 
[151] parallel_4.5.2                    pROC_1.19.0.1                     jsonlite_2.0.0                   
[154] RcppHNSW_0.6.0                    TFBSTools_1.48.0                  bitops_1.0-9                     
[157] bit64_4.6.0-1                     Rtsne_0.17                        alabaster.matrix_1.10.0          
[160] spatstat.utils_3.2-1              BiocNeighbors_2.4.0               jquerylib_0.1.4                  
[163] alabaster.se_1.10.0               shinyjs_2.1.1                     spatstat.univar_3.1-6            
[166] timeDate_4052.112                 lazyeval_0.2.2                    alabaster.base_1.10.0            
[169] shiny_1.12.1                      htmltools_0.5.9                   sctransform_0.4.3                
[172] rappdirs_0.3.4                    ensembldb_2.34.0                  glue_1.8.0                       
[175] TFMPvalue_1.0.0                   spam_2.11-3                       googlesheets4_1.1.2              
[178] httr2_1.2.2                       XVector_0.50.0                    RCurl_1.98-1.17                  
[181] BSgenome_1.78.0                   gridExtra_2.3                     JASPAR2020_0.99.10               
[184] igraph_2.2.1                      R6_2.6.1                          RcppRoll_0.3.1                   
[187] GenomicFeatures_1.62.0            cluster_2.1.8.1                   gargle_1.6.0                     
[190] Rhdf5lib_1.32.0                   GenomeInfoDb_1.46.2               ipred_0.9-15                     
[193] DirichletMultinomial_1.52.0       DelayedArray_0.36.0               tidyselect_1.2.1                 
[196] vipor_0.4.7                       ProtGenerics_1.42.0               xml2_1.5.2                       
[199] AnnotationDbi_1.72.0              future_1.69.0                     ModelMetrics_1.2.2.2             
[202] KernSmooth_2.23-26                S7_0.2.1                          htmlwidgets_1.6.4                
[205] rlang_1.1.7                       spatstat.sparse_3.1-0             spatstat.explore_3.7-0           
[208] hardhat_1.4.2                     beeswarm_0.4.0                    prodlim_2025.04.28               
LS0tCnRpdGxlOiAiQ3Jvc3MtTWV0aG9kIEFubm90YXRpb24gQ29tcGFyaXNvbiAtIENlbGwgbGluZXMgU8OpemFyeSBTeW5kcm9tZSBEYXRhc2V0IgphdXRob3I6IE5hc2lyIE1haG1vb2QgQWJiYXNpCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IHRydWUKICAgIHRoZW1lOiBqb3VybmFsCi0tLQoKCgojIGxvYWQgbGlicmFyaWVzCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZWNobyA9IFRSVUUsCiAgd2FybmluZyA9IEZBTFNFLAogIG1lc3NhZ2UgPSBGQUxTRSwKICBmaWcud2lkdGggPSAxNCwKICBmaWcuaGVpZ2h0ID0gOCwKICBkcGkgPSAzMDAKKQojIGxvYWQgbGlicmFyaWVzCiAgICBsaWJyYXJ5KFNldXJhdCkKICAgIGxpYnJhcnkoZHBseXIpCiAgICBsaWJyYXJ5KHBhdGNod29yaykKICAgIGxpYnJhcnkoZ2dwbG90MikKICAgIGxpYnJhcnkocGhlYXRtYXApCiAgICBsaWJyYXJ5KHNjUHJlZCkKICAgIGxpYnJhcnkoY2VsbGRleCkKICAgIGxpYnJhcnkoU2luZ2xlUikKICAgIGxpYnJhcnkocmVtb3RlcykKICAgIGxpYnJhcnkocHJlc3RvKQogICAgbGlicmFyeShTZXVyYXREaXNrKQogICAgbGlicmFyeShTZXVyYXREYXRhKQogICAgbGlicmFyeShBemltdXRoKQoKYGBgCgoKCgojIExvYWQgUkRTIHdpdGggYWxsIGFubm90YXRpb25zCmBgYHtyfQojIExvYWQgbWFpbiBvYmplY3Qgd2l0aCBhbGwgYW5ub3RhdGlvbnMKQWxsX3NhbXBsZXNfTWVyZ2VkIDwtIHJlYWRSRFMoIi9ob21lL2Jpb2luZm8vMS1UaGVzaXNfRmluYWxfWWVhcl8yMDI1LzIwMjUtWWVhcjNfQW5hbHlzaXMvMS1zY1JOQV9SRVNVTFRTLTE5LTExLTIwMjUvMi1CZW5jaG1hcmtpbmctQW5ub3RhdGlvbl9tZXRob2RzXzA1LTExLTIwMjUvQWxsX3NhbXBsZXNfTWVyZ2VkX0JlbmNobWFya2VkXzA1LTExLTIwMjUucmRzIikKCgpJZGVudHMoQWxsX3NhbXBsZXNfTWVyZ2VkKSA8LSAic2V1cmF0X2NsdXN0ZXJzIgoKIyBFbnN1cmUgY2x1c3RlcnMgYXJlIG9yZGVyZWQgMC0xMwpBbGxfc2FtcGxlc19NZXJnZWQkc2V1cmF0X2NsdXN0ZXJzIDwtIGZhY3RvcihBbGxfc2FtcGxlc19NZXJnZWQkc2V1cmF0X2NsdXN0ZXJzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGFzLmNoYXJhY3RlcigwOjEzKSkKCgoKCmNhdCgiVG90YWwgY2VsbHM6IiwgbmNvbChBbGxfc2FtcGxlc19NZXJnZWQpLCAiXG4iKQpjYXQoIlRvdGFsIGNsdXN0ZXJzOiIsIGxlbmd0aCh1bmlxdWUoQWxsX3NhbXBsZXNfTWVyZ2VkJHNldXJhdF9jbHVzdGVycykpLCAiXG5cbiIpCmNhdCgiQ2x1c3RlcnMgKG9yZGVyZWQgMC0xOCk6XG4iKQpwcmludCh0YWJsZShBbGxfc2FtcGxlc19NZXJnZWQkc2V1cmF0X2NsdXN0ZXJzKSkKCmNhdCgiXG5cblNhbXBsZXMgKG9yZGVyZWQpOlxuIikKcHJpbnQodGFibGUoQWxsX3NhbXBsZXNfTWVyZ2VkJGNlbGxfbGluZSkpCgpgYGAKIyBDcmVhdGUgQW5ub3RhdGlvbiBTdW1tYXJ5IFRhYmxlCiMjIENyZWF0ZSBBbm5vdGF0aW9uIFN1bW1hcnkgVGFibGUgYnkgQ2x1c3RlcnMKYGBge3IgfQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCgojIEV4dHJhY3QgZG9taW5hbnQgbGFiZWwgcGVyIGNsdXN0ZXIgcGVyIG1ldGhvZApjb2xzX25lZWRlZCA8LSBjKCJzZXVyYXRfY2x1c3RlcnMiLCAicHJlZGljdGVkLmlkIiwgInByZWRpY3RlZC5jZWxsdHlwZS5sMiIsIAogICAgICAgICAgICAgICAgICJzaW5nbGVyLmltbXVuZSIsICJzY0FUT01JQ19hbm5vdGF0aW9uIikKCmFubm90YXRpb25fdGFibGVfY2x1c3RlciA8LSBBbGxfc2FtcGxlc19NZXJnZWRAbWV0YS5kYXRhWywgY29sc19uZWVkZWRdICU+JQogIGdyb3VwX2J5KHNldXJhdF9jbHVzdGVycykgJT4lCiAgc3VtbWFyaXNlKAogICAgc2NQcmVkID0gbmFtZXMoc29ydCh0YWJsZShwcmVkaWN0ZWQuaWQpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgQXppbXV0aCA9IG5hbWVzKHNvcnQodGFibGUocHJlZGljdGVkLmNlbGx0eXBlLmwyKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIFNpbmdsZVIgPSBuYW1lcyhzb3J0KHRhYmxlKHNpbmdsZXIuaW1tdW5lKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIHNjQVRPTUlDID0gbmFtZXMoc29ydCh0YWJsZShzY0FUT01JQ19hbm5vdGF0aW9uKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIG5fY2VsbHMgPSBuKCksCiAgICAuZ3JvdXBzID0gImRyb3AiCiAgKSAlPiUKICBhcnJhbmdlKHNldXJhdF9jbHVzdGVycykKCiMgQWRkIGNvbnNlbnN1cyBjb2x1bW4KYW5ub3RhdGlvbl90YWJsZV9jbHVzdGVyIDwtIGFubm90YXRpb25fdGFibGVfY2x1c3RlciAlPiUKICBtdXRhdGUoCiAgICBDb25zZW5zdXMgPSAoc2NQcmVkID09IEF6aW11dGggJiBBemltdXRoID09IFNpbmdsZVIgJiBTaW5nbGVSID09IHNjQVRPTUlDKQogICkKCiMgRGlzcGxheQpjYXQoIlxuPT09IEFOTk9UQVRJT04gQlkgQ0xVU1RFUiA9PT1cblxuIikKa2FibGUoYW5ub3RhdGlvbl90YWJsZV9jbHVzdGVyLCBjYXB0aW9uID0gIkRvbWluYW50IENlbGwgVHlwZSBBbm5vdGF0aW9uIHBlciBDbHVzdGVyIikgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiksIAogICAgICAgICAgICAgICAgZnVsbF93aWR0aCA9IEZBTFNFLCBmb250X3NpemUgPSAxMCkgJT4lCiAgY29sdW1uX3NwZWMoNywgYmFja2dyb3VuZCA9IGlmZWxzZShhbm5vdGF0aW9uX3RhYmxlX2NsdXN0ZXIkQ29uc2Vuc3VzLCAiI2Q0ZWRkYSIsICIjZjhkN2RhIikpCgojIFNhdmUKd3JpdGUuY3N2KGFubm90YXRpb25fdGFibGVfY2x1c3RlciwgIkNlbGxMaW5lc19hbm5vdGF0aW9uX0JZX0NMVVNURVJfMDQtMDItMjAyNi5jc3YiLCAKICAgICAgICAgIHJvdy5uYW1lcyA9IEZBTFNFKQoKY2F0KCJcbuKckyBBbm5vdGF0aW9uIHRhYmxlIGJ5IGNsdXN0ZXIgc2F2ZWRcbiIpCgpgYGAKCgojIyBDcmVhdGUgQW5ub3RhdGlvbiBTdW1tYXJ5IFRhYmxlIC0gQlkgQ0VMTCBMSU5FCmBgYHtyIH0KCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKCiMgRXh0cmFjdCBkb21pbmFudCBsYWJlbCBwZXIgQ0VMTCBMSU5FIHBlciBtZXRob2QKY29sc19uZWVkZWQgPC0gYygiY2VsbF9saW5lIiwgInByZWRpY3RlZC5pZCIsICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiLCAKICAgICAgICAgICAgICAgICAic2luZ2xlci5pbW11bmUiLCAic2NBVE9NSUNfYW5ub3RhdGlvbiIpCgphbm5vdGF0aW9uX3RhYmxlX2NlbGxsaW5lIDwtIEFsbF9zYW1wbGVzX01lcmdlZEBtZXRhLmRhdGFbLCBjb2xzX25lZWRlZF0gJT4lCiAgZ3JvdXBfYnkoY2VsbF9saW5lKSAlPiUKICBzdW1tYXJpc2UoCiAgIHNjUHJlZCA9IG5hbWVzKHNvcnQodGFibGUocHJlZGljdGVkLmlkKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIEF6aW11dGggPSBuYW1lcyhzb3J0KHRhYmxlKHByZWRpY3RlZC5jZWxsdHlwZS5sMiksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICBTaW5nbGVSID0gbmFtZXMoc29ydCh0YWJsZShzaW5nbGVyLmltbXVuZSksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICBzY0FUT01JQyA9IG5hbWVzKHNvcnQodGFibGUoc2NBVE9NSUNfYW5ub3RhdGlvbiksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICBuX2NlbGxzID0gbigpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkgJT4lCiAgYXJyYW5nZShjZWxsX2xpbmUpCgojIEFkZCBjb25zZW5zdXMgY29sdW1uCmFubm90YXRpb25fdGFibGVfY2VsbGxpbmUgPC0gYW5ub3RhdGlvbl90YWJsZV9jZWxsbGluZSAlPiUKICBtdXRhdGUoCiAgICBDb25zZW5zdXMgPSAoc2NQcmVkID09IEF6aW11dGggJiBBemltdXRoID09IFNpbmdsZVIgJiBTaW5nbGVSID09IHNjQVRPTUlDKQogICkKCiMgRGlzcGxheQpjYXQoIlxuPT09IEFOTk9UQVRJT04gQlkgQ0VMTCBMSU5FID09PVxuXG4iKQprYWJsZShhbm5vdGF0aW9uX3RhYmxlX2NlbGxsaW5lLCBjYXB0aW9uID0gIkRvbWluYW50IENlbGwgVHlwZSBBbm5vdGF0aW9uIHBlciBDZWxsIExpbmUiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSwgCiAgICAgICAgICAgICAgICBmdWxsX3dpZHRoID0gRkFMU0UsIGZvbnRfc2l6ZSA9IDEwKSAlPiUKICBjb2x1bW5fc3BlYyg3LCBiYWNrZ3JvdW5kID0gaWZlbHNlKGFubm90YXRpb25fdGFibGVfY2VsbGxpbmUkQ29uc2Vuc3VzLCAiI2Q0ZWRkYSIsICIjZjhkN2RhIikpCgojIFNhdmUKd3JpdGUuY3N2KGFubm90YXRpb25fdGFibGVfY2VsbGxpbmUsICJDZWxsTGluZXNfYW5ub3RhdGlvbl9CWV9DRUxMTElORV8wNC0wMi0yMDI2LmNzdiIsIAogICAgICAgICAgcm93Lm5hbWVzID0gRkFMU0UpCgpjYXQoIlxu4pyTIEFubm90YXRpb24gdGFibGUgYnkgY2VsbCBsaW5lIHNhdmVkXG4iKQoKYGBgCgojIyBDcmVhdGUgQW5ub3RhdGlvbiBTdW1tYXJ5IFRhYmxlIC0gQ29tYmluZWQgVGFibGUgKENsdXN0ZXIgw5cgQ2VsbCBMaW5lKQpgYGB7ciB9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKCiMgRXh0cmFjdCBkb21pbmFudCBsYWJlbCBwZXIgQ0xVU1RFUiDDlyBDRUxMIExJTkUgw5cgTUVUSE9ECmNvbHNfbmVlZGVkIDwtIGMoInNldXJhdF9jbHVzdGVycyIsICJjZWxsX2xpbmUiLCAicHJlZGljdGVkLmlkIiwgInByZWRpY3RlZC5jZWxsdHlwZS5sMiIsIAogICAgICAgICAgICAgICAgICJzaW5nbGVyLmltbXVuZSIsICJzY0FUT01JQ19hbm5vdGF0aW9uIikKCmFubm90YXRpb25fdGFibGVfY29tYmluZWQgPC0gQWxsX3NhbXBsZXNfTWVyZ2VkQG1ldGEuZGF0YVssIGNvbHNfbmVlZGVkXSAlPiUKICBncm91cF9ieShzZXVyYXRfY2x1c3RlcnMsIGNlbGxfbGluZSkgJT4lCiAgc3VtbWFyaXNlKAogICBzY1ByZWQgPSBuYW1lcyhzb3J0KHRhYmxlKHByZWRpY3RlZC5pZCksIGRlY3JlYXNpbmcgPSBUUlVFKSlbMV0sCiAgICBBemltdXRoID0gbmFtZXMoc29ydCh0YWJsZShwcmVkaWN0ZWQuY2VsbHR5cGUubDIpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgU2luZ2xlUiA9IG5hbWVzKHNvcnQodGFibGUoc2luZ2xlci5pbW11bmUpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgc2NBVE9NSUMgPSBuYW1lcyhzb3J0KHRhYmxlKHNjQVRPTUlDX2Fubm90YXRpb24pLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgbl9jZWxscyA9IG4oKSwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApICU+JQogIGFycmFuZ2UoY2VsbF9saW5lLCBzZXVyYXRfY2x1c3RlcnMpCgojIEFkZCBjb25zZW5zdXMgY29sdW1uCmFubm90YXRpb25fdGFibGVfY29tYmluZWQgPC0gYW5ub3RhdGlvbl90YWJsZV9jb21iaW5lZCAlPiUKICBtdXRhdGUoCiAgICBDb25zZW5zdXMgPSAoc2NQcmVkID09IEF6aW11dGggJiBBemltdXRoID09IFNpbmdsZVIgJiBTaW5nbGVSID09IHNjQVRPTUlDKQogICkKCiMgRGlzcGxheSAoZmlyc3QgMjAgcm93cykKY2F0KCJcbj09PSBBTk5PVEFUSU9OIEJZIENMVVNURVIgw5cgQ0VMTCBMSU5FIChGaXJzdCAyMCByb3dzKSA9PT1cblxuIikKa2FibGUoaGVhZChhbm5vdGF0aW9uX3RhYmxlX2NvbWJpbmVkLCAyMCksIAogICAgICBjYXB0aW9uID0gIkRvbWluYW50IEFubm90YXRpb24gcGVyIENsdXN0ZXIgw5cgQ2VsbCBMaW5lIikgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiksIAogICAgICAgICAgICAgICAgZnVsbF93aWR0aCA9IEZBTFNFLCBmb250X3NpemUgPSA5KSAlPiUKICBjb2x1bW5fc3BlYyg4LCBiYWNrZ3JvdW5kID0gaWZlbHNlKGhlYWQoYW5ub3RhdGlvbl90YWJsZV9jb21iaW5lZCRDb25zZW5zdXMsIDIwKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiNkNGVkZGEiLCAiI2Y4ZDdkYSIpKQoKIyBTYXZlCndyaXRlLmNzdihhbm5vdGF0aW9uX3RhYmxlX2NvbWJpbmVkLCAKICAgICAgICAgICJDZWxsTGluZXNfYW5ub3RhdGlvbl9CWV9DTFVTVEVSX0FORF9DRUxMTElORV8wNC0wMi0yMDI2LmNzdiIsIAogICAgICAgICAgcm93Lm5hbWVzID0gRkFMU0UpCgpjYXQoIlxu4pyTIENvbWJpbmVkIGFubm90YXRpb24gdGFibGUgKGNsdXN0ZXIgw5cgY2VsbCBsaW5lKSBzYXZlZFxuIikKY2F0KCJUb3RhbCByb3dzOiIsIG5yb3coYW5ub3RhdGlvbl90YWJsZV9jb21iaW5lZCksICJcbiIpCgpgYGAKIyBDcmVhdGUgQW5ub3RhdGlvbiBTdW1tYXJ5CmBgYHtyIH0KbGlicmFyeShwdXJycikgICMgTWFrZSBzdXJlIHRoaXMgaXMgbG9hZGVkCgojIERlZmluZSBhbm5vdGF0aW9uIG1ldGhvZHMKbWV0aG9kcyA8LSBjKAogICJwcmVkaWN0ZWQuaWQiLCAgICAgICAgICAgICAjIHNjUHJlZAogICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiLCAgICAjIEF6aW11dGggKGwyIHByZWRpY3Rpb24pCiAgInNpbmdsZXIuaW1tdW5lIiwgICAgICAgICAgICMgU2luZ2xlUgogICJzY0FUT01JQ19hbm5vdGF0aW9uIiAgICAgICAjIHNjQVRPTUlDCikKCiMgQ3JlYXRlIHN1bW1hcnkgLSBtb3N0IGNvbW1vbiBsYWJlbCBwZXIgY2x1c3RlciBmb3IgZWFjaCBtZXRob2QKYW5ub3RhdGlvbl9zdW1tYXJ5IDwtIG1hcF9kZnIobWV0aG9kcywgZnVuY3Rpb24obSkgewogIGRmIDwtIEFsbF9zYW1wbGVzX01lcmdlZEBtZXRhLmRhdGEKICBkZiAlPiUKICAgIGZpbHRlcighaXMubmEoLmRhdGFbW21dXSkpICU+JQogICAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzLCBsYWJlbCA9IC5kYXRhW1ttXV0pICU+JQogICAgc3VtbWFyaXNlKG4gPSBuKCksIC5ncm91cHMgPSAiZHJvcCIpICU+JQogICAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzKSAlPiUKICAgIHNsaWNlX21heChuLCBuID0gMSwgd2l0aF90aWVzID0gRkFMU0UpICU+JQogICAgbXV0YXRlKG1ldGhvZCA9IG0pCn0pCgojIFJlbmFtZSBtZXRob2RzIGZvciBkaXNwbGF5CmFubm90YXRpb25fc3VtbWFyeSA8LSBhbm5vdGF0aW9uX3N1bW1hcnkgJT4lCiAgbXV0YXRlKG1ldGhvZCA9IHJlY29kZShtZXRob2QsCiAgICAgICAgICAgICAgICAgICAgICAgICAicHJlZGljdGVkLmlkIiA9ICJzY1ByZWQiLAogICAgICAgICAgICAgICAgICAgICAgICAgInByZWRpY3RlZC5jZWxsdHlwZS5sMiIgPSAiQXppbXV0aC5sMiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAic2luZ2xlci5pbW11bmUiID0gIlNpbmdsZVIoSW1tdW5lKSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAic2NBVE9NSUNfYW5ub3RhdGlvbiIgPSAic2NBVE9NSUMiKSkKCiMgU2V0IG1ldGhvZCBvcmRlcgphbm5vdGF0aW9uX3N1bW1hcnkkbWV0aG9kIDwtIGZhY3Rvcihhbm5vdGF0aW9uX3N1bW1hcnkkbWV0aG9kLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygic2NQcmVkIiwgIkF6aW11dGgubDIiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTaW5nbGVSKEltbXVuZSkiLCAic2NBVE9NSUMiKSkKCiMgRW5zdXJlIGNsdXN0ZXIgb3JkZXIKYW5ub3RhdGlvbl9zdW1tYXJ5JHNldXJhdF9jbHVzdGVycyA8LSBmYWN0b3IoYW5ub3RhdGlvbl9zdW1tYXJ5JHNldXJhdF9jbHVzdGVycywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGFzLmNoYXJhY3RlcigwOjEzKSkgICMgQWRqdXN0IHJhbmdlCgoKYGBgCgojIyBTdW1tYXJ5IFN0YXRpc3RpY3MKYGBge3IgfQojIENlbGwgY291bnRzIHBlciBtZXRob2QKY2F0KCJcbj09PSBDZWxscyBBbm5vdGF0ZWQgUGVyIE1ldGhvZCA9PT1cblxuIikKbWV0aG9kX2NvbHMgPC0gYygicHJlZGljdGVkLmlkIiwgInByZWRpY3RlZC5jZWxsdHlwZS5sMiIsICJzaW5nbGVyLmltbXVuZSIsICJzY0FUT01JQ19hbm5vdGF0aW9uIikKZm9yKG0gaW4gbWV0aG9kX2NvbHMpewogIG5fYW5ub3RhdGVkIDwtIHN1bSghaXMubmEoQWxsX3NhbXBsZXNfTWVyZ2VkQG1ldGEuZGF0YVtbbV1dKSkKICBjYXQoc3ByaW50ZigiJS0yNXM6ICU1ZCBjZWxscyAoJS4xZiUlKVxuIiwgCiAgICAgICAgICAgICAgbSwgbl9hbm5vdGF0ZWQsIG5fYW5ub3RhdGVkL25jb2woQWxsX3NhbXBsZXNfTWVyZ2VkKSoxMDApKQp9CgojIE51bWJlciBvZiB1bmlxdWUgbGFiZWxzIHBlciBtZXRob2QKY2F0KCJcbj09PSBVbmlxdWUgTGFiZWxzIFBlciBNZXRob2QgPT09XG5cbiIpCmFubm90YXRpb25fc3VtbWFyeSAlPiUKICBncm91cF9ieShtZXRob2QpICU+JQogIHN1bW1hcmlzZShuX3VuaXF1ZV9sYWJlbHMgPSBuX2Rpc3RpbmN0KGxhYmVsKSkgJT4lCiAgcHJpbnQoKQoKYGBgCiMgQmFzaWMgSGVhdG1hcCBWaXN1YWxpemF0aW9uCmBgYHtyLCBmaWcuaGVpZ2h0PSA1LCBmaWcud2lkdGg9IDE4fQpsaWJyYXJ5KGdncGxvdDIpCgpnZ3Bsb3QoYW5ub3RhdGlvbl9zdW1tYXJ5LCBhZXMoeCA9IHNldXJhdF9jbHVzdGVycywgeSA9IG1ldGhvZCwgZmlsbCA9IGxhYmVsKSkgKwogIGdlb21fdGlsZShjb2xvciA9ICJ3aGl0ZSIsIGxpbmV3aWR0aCA9IDAuNSkgKwogIHNjYWxlX2ZpbGxfZGlzY3JldGUoKSArCiAgbGFicygKICAgIHggPSAiU2V1cmF0IENsdXN0ZXJzIiwKICAgIHkgPSAiQW5ub3RhdGlvbiBNZXRob2QiLAogICAgZmlsbCA9ICJBc3NpZ25lZCBDZWxsIFR5cGUiLAogICAgdGl0bGUgPSAiQ3Jvc3MtTWV0aG9kIENvbXBhcmlzb24gb2YgQ2VsbCBUeXBlIEFubm90YXRpb25zIgogICkgKwogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTQpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE2KQogICkKYGBgCgojIyBFbmhhbmNlZCBIZWF0bWFwIHdpdGggQ3VzdG9tIENvbG9ycwpgYGB7ciwgZmlnLmhlaWdodD0gNSwgZmlnLndpZHRoPSAxOCB9CmxpYnJhcnkoUkNvbG9yQnJld2VyKQoKIyBHZW5lcmF0ZSBjb2xvciBwYWxldHRlCm51bV9jb2xvcnMgPC0gbGVuZ3RoKHVuaXF1ZShhbm5vdGF0aW9uX3N1bW1hcnkkbGFiZWwpKQpteV9jb2xvcnMgPC0gY29sb3JSYW1wUGFsZXR0ZShicmV3ZXIucGFsKDgsICJTZXQyIikpKG51bV9jb2xvcnMpCgpwX2hlYXRtYXAgPC0gZ2dwbG90KGFubm90YXRpb25fc3VtbWFyeSwgYWVzKHggPSBzZXVyYXRfY2x1c3RlcnMsIHkgPSBtZXRob2QsIGZpbGwgPSBsYWJlbCkpICsKICBnZW9tX3RpbGUoY29sb3IgPSAid2hpdGUiLCBsaW5ld2lkdGggPSAwLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBteV9jb2xvcnMpICsKICBzY2FsZV94X2Rpc2NyZXRlKGRyb3AgPSBGQUxTRSkgKyAgIyBLZWVwIGFsbCBjbHVzdGVyIGxldmVscwogIGxhYnMoCiAgICB4ID0gIlNldXJhdCBDbHVzdGVycyIsCiAgICB5ID0gIkFubm90YXRpb24gTWV0aG9kIiwKICAgIGZpbGwgPSAiQXNzaWduZWQgQ2VsbCBUeXBlIiwKICAgIHRpdGxlID0gIkNyb3NzLU1ldGhvZCBDb21wYXJpc29uIG9mIENlbGwgVHlwZSBBbm5vdGF0aW9ucyIKICApICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNiksCiAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiCiAgKQoKcHJpbnQocF9oZWF0bWFwKQoKIyBTYXZlIGhpZ2gtcXVhbGl0eSB2ZXJzaW9ucwpnZ3NhdmUoIkNlbGxMaW5lc19hbm5vdGF0aW9uX2hlYXRtYXBfMDQtMDItMjAyNi5wbmciLCBwbG90ID0gcF9oZWF0bWFwLCAKICAgICAgIHdpZHRoID0gMTQsIGhlaWdodCA9IDUsIGRwaSA9IDMwMCwgYmcgPSAid2hpdGUiKQpnZ3NhdmUoIkNlbGxMaW5lc19hbm5vdGF0aW9uX2hlYXRtYXBfMDQtMDItMjAyNi5wZGYiLCBwbG90ID0gcF9oZWF0bWFwLCAKICAgICAgIHdpZHRoID0gMTQsIGhlaWdodCA9IDUpCgpjYXQoIlxu4pyTIEhlYXRtYXAgc2F2ZWQgYXMgUE5HIGFuZCBQREZcbiIpCgoKYGBgCiMgIE1BTlVTQ1JJUFQgRklHVVJFCiMjICBNQU5VU0NSSVBUIEZJR1VSRTogVGV4dC1CYXNlZCBIZWF0bWFwIEJZIENMVVNURVJTCmBgYHtyLCBmaWcuaGVpZ2h0PSA1LCBmaWcud2lkdGg9IDMwIH0KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQoKIyBQcmVwYXJlIGRhdGEgYnkgQ0xVU1RFUlMKY29sc19uZWVkZWQgPC0gYygic2V1cmF0X2NsdXN0ZXJzIiwgInByZWRpY3RlZC5pZCIsICJwcmVkaWN0ZWQuY2VsbHR5cGUubDIiLCAKICAgICAgICAgICAgICAgICAic2luZ2xlci5pbW11bmUiLCAic2NBVE9NSUNfYW5ub3RhdGlvbiIpCgpoZWF0bWFwX2RhdGFfY2x1c3RlcnMgPC0gQWxsX3NhbXBsZXNfTWVyZ2VkQG1ldGEuZGF0YVssIGNvbHNfbmVlZGVkXSAlPiUKICBncm91cF9ieShzZXVyYXRfY2x1c3RlcnMpICU+JQogIHN1bW1hcmlzZSgKICAgc2NQcmVkID0gbmFtZXMoc29ydCh0YWJsZShwcmVkaWN0ZWQuaWQpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgQXppbXV0aCA9IG5hbWVzKHNvcnQodGFibGUocHJlZGljdGVkLmNlbGx0eXBlLmwyKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIFNpbmdsZVIgPSBuYW1lcyhzb3J0KHRhYmxlKHNpbmdsZXIuaW1tdW5lKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIHNjQVRPTUlDID0gbmFtZXMoc29ydCh0YWJsZShzY0FUT01JQ19hbm5vdGF0aW9uKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhzY1ByZWQsIEF6aW11dGgsIFNpbmdsZVIsIHNjQVRPTUlDKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiTWV0aG9kIiwgdmFsdWVzX3RvID0gIkNlbGxUeXBlIikgJT4lCiAgbXV0YXRlKAogICAgTWV0aG9kID0gZmFjdG9yKE1ldGhvZCwgbGV2ZWxzID0gYygic2NBVE9NSUMiLCAiU2luZ2xlUiIsICJBemltdXRoIiwgInNjUHJlZCIpKSwKICAgIHNldXJhdF9jbHVzdGVycyA9IGZhY3RvcihzZXVyYXRfY2x1c3RlcnMsIGxldmVscyA9IGFzLmNoYXJhY3RlcigwOjEzKSksICAjIENIQU5HRSBSQU5HRQogICAgQ2VsbFR5cGVfZGlzcGxheSA9IGlmZWxzZShuY2hhcihDZWxsVHlwZSkgPiAyMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMChzdWJzdHIoQ2VsbFR5cGUsIDEsIDE3KSwgIi4uLiIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2VsbFR5cGUpCiAgKQoKIyBDcmVhdGUgbWFudXNjcmlwdCBmaWd1cmUgQlkgQ0xVU1RFUlMKcF9tYW51c2NyaXB0X2NsdXN0ZXJzIDwtIGdncGxvdChoZWF0bWFwX2RhdGFfY2x1c3RlcnMsIAogICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gc2V1cmF0X2NsdXN0ZXJzLCB5ID0gTWV0aG9kLCBsYWJlbCA9IENlbGxUeXBlX2Rpc3BsYXkpKSArCiAgZ2VvbV90aWxlKGNvbG9yID0gImdyZXkyMCIsIGZpbGwgPSAid2hpdGUiLCBsaW5ld2lkdGggPSAwLjgpICsKICBnZW9tX3RleHQoc2l6ZSA9IDMsIGhqdXN0ID0gMC41LCB2anVzdCA9IDAuNSwgZm9udGZhY2UgPSAiYm9sZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRGlzY29yZGFuY2UgaW4gQXV0b21hdGVkIENlbGwgVHlwZSBBbm5vdGF0aW9uIC0gQ2VsbCBMaW5lcyBieSBDbHVzdGVycyIsCiAgICB4ID0gIlNldXJhdCBDbHVzdGVyIiwKICAgIHkgPSAiQW5ub3RhdGlvbiBNZXRob2QiCiAgKSArCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxNikgKwogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE0KSwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNCksCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiwgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBmYWNlID0gImJvbGQiLCBzaXplID0gMTgpLAogICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKDEwLCAxMCwgMTAsIDEwKQogICkKCnByaW50KHBfbWFudXNjcmlwdF9jbHVzdGVycykKCiMgU2F2ZSBoaWdoLXF1YWxpdHkgdmVyc2lvbnMKZ2dzYXZlKCJGaWd1cmVYX0NlbGxMaW5lc19Bbm5vdGF0aW9uX0JZX0NMVVNURVJTLnBuZyIsIAogICAgICAgcGxvdCA9IHBfbWFudXNjcmlwdF9jbHVzdGVycywgd2lkdGggPSAzMCwgaGVpZ2h0ID0gNSwgZHBpID0gNjAwLCBiZyA9ICJ3aGl0ZSIpCmdnc2F2ZSgiRmlndXJlWF9DZWxsTGluZXNfQW5ub3RhdGlvbl9CWV9DTFVTVEVSUy5wZGYiLCAKICAgICAgIHBsb3QgPSBwX21hbnVzY3JpcHRfY2x1c3RlcnMsIHdpZHRoID0gMzAsIGhlaWdodCA9IDUpCmdnc2F2ZSgiRmlndXJlWF9DZWxsTGluZXNfQW5ub3RhdGlvbl9CWV9DTFVTVEVSUy50aWZmIiwgCiAgICAgICBwbG90ID0gcF9tYW51c2NyaXB0X2NsdXN0ZXJzLCB3aWR0aCA9IDMwLCBoZWlnaHQgPSA1LCBkcGkgPSA2MDAsIGJnID0gIndoaXRlIikKCmNhdCgiXG7inJMgTWFudXNjcmlwdCBmaWd1cmUgQlkgQ0xVU1RFUlMgc2F2ZWQgKFBORywgUERGLCBUSUZGIGF0IDYwMCBEUEkpXG4iKQoKYGBgCgojIyAgTUFOVVNDUklQVCBGSUdVUkU6IFRleHQtQmFzZWQgSGVhdG1hcCBCWSBDRUxMIExJTkVTCmBgYHtyLCBmaWcuaGVpZ2h0PSA1LCBmaWcud2lkdGg9IDE4IH0KIyBQcmVwYXJlIGRhdGEgYnkgQ0VMTCBMSU5FUwpjb2xzX25lZWRlZF9jZWxsbGluZSA8LSBjKCJjZWxsX2xpbmUiLCAicHJlZGljdGVkLmlkIiwgInByZWRpY3RlZC5jZWxsdHlwZS5sMiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICJzaW5nbGVyLmltbXVuZSIsICJzY0FUT01JQ19hbm5vdGF0aW9uIikKCmhlYXRtYXBfZGF0YV9jZWxsbGluZXMgPC0gQWxsX3NhbXBsZXNfTWVyZ2VkQG1ldGEuZGF0YVssIGNvbHNfbmVlZGVkX2NlbGxsaW5lXSAlPiUKICBncm91cF9ieShjZWxsX2xpbmUpICU+JQogIHN1bW1hcmlzZSgKICAgc2NQcmVkID0gbmFtZXMoc29ydCh0YWJsZShwcmVkaWN0ZWQuaWQpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgQXppbXV0aCA9IG5hbWVzKHNvcnQodGFibGUocHJlZGljdGVkLmNlbGx0eXBlLmwyKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIFNpbmdsZVIgPSBuYW1lcyhzb3J0KHRhYmxlKHNpbmdsZXIuaW1tdW5lKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIHNjQVRPTUlDID0gbmFtZXMoc29ydCh0YWJsZShzY0FUT01JQ19hbm5vdGF0aW9uKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhzY1ByZWQsIEF6aW11dGgsIFNpbmdsZVIsIHNjQVRPTUlDKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiTWV0aG9kIiwgdmFsdWVzX3RvID0gIkNlbGxUeXBlIikgJT4lCiAgbXV0YXRlKAogICAgTWV0aG9kID0gZmFjdG9yKE1ldGhvZCwgbGV2ZWxzID0gYygic2NBVE9NSUMiLCAiU2luZ2xlUiIsICJBemltdXRoIiwgInNjUHJlZCIpKSwKICAgIENlbGxUeXBlX2Rpc3BsYXkgPSBpZmVsc2UobmNoYXIoQ2VsbFR5cGUpID4gMjAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAoc3Vic3RyKENlbGxUeXBlLCAxLCAxNyksICIuLi4iKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENlbGxUeXBlKQogICkKCiMgQ3JlYXRlIG1hbnVzY3JpcHQgZmlndXJlIEJZIENFTEwgTElORVMKcF9tYW51c2NyaXB0X2NlbGxsaW5lcyA8LSBnZ3Bsb3QoaGVhdG1hcF9kYXRhX2NlbGxsaW5lcywgCiAgICAgICAgICAgICAgICAgICAgICAgYWVzKHggPSBjZWxsX2xpbmUsIHkgPSBNZXRob2QsIGxhYmVsID0gQ2VsbFR5cGVfZGlzcGxheSkpICsKICBnZW9tX3RpbGUoY29sb3IgPSAiZ3JleTIwIiwgZmlsbCA9ICJ3aGl0ZSIsIGxpbmV3aWR0aCA9IDAuOCkgKwogIGdlb21fdGV4dChzaXplID0gMywgaGp1c3QgPSAwLjUsIHZqdXN0ID0gMC41LCBmb250ZmFjZSA9ICJib2xkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJEaXNjb3JkYW5jZSBpbiBBdXRvbWF0ZWQgQ2VsbCBUeXBlIEFubm90YXRpb24gLSBCeSBDZWxsIExpbmUiLAogICAgeCA9ICJDZWxsIExpbmUiLAogICAgeSA9ICJBbm5vdGF0aW9uIE1ldGhvZCIKICApICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE2KSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBzaXplID0gMTQsIGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBzaXplID0gMTQpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYsIGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE4KSwKICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigxMCwgMTAsIDEwLCAxMCkKICApCgpwcmludChwX21hbnVzY3JpcHRfY2VsbGxpbmVzKQoKIyBTYXZlIGhpZ2gtcXVhbGl0eSB2ZXJzaW9ucwpnZ3NhdmUoIkZpZ3VyZVhfQ2VsbExpbmVzX0Fubm90YXRpb25fQllfQ0VMTExJTkUucG5nIiwgCiAgICAgICBwbG90ID0gcF9tYW51c2NyaXB0X2NlbGxsaW5lcywgd2lkdGggPSAxNCwgaGVpZ2h0ID0gNSwgZHBpID0gNjAwLCBiZyA9ICJ3aGl0ZSIpCmdnc2F2ZSgiRmlndXJlWF9DZWxsTGluZXNfQW5ub3RhdGlvbl9CWV9DRUxMTElORS5wZGYiLCAKICAgICAgIHBsb3QgPSBwX21hbnVzY3JpcHRfY2VsbGxpbmVzLCB3aWR0aCA9IDE0LCBoZWlnaHQgPSA1KQpnZ3NhdmUoIkZpZ3VyZVhfQ2VsbExpbmVzX0Fubm90YXRpb25fQllfQ0VMTExJTkUudGlmZiIsIAogICAgICAgcGxvdCA9IHBfbWFudXNjcmlwdF9jZWxsbGluZXMsIHdpZHRoID0gMTQsIGhlaWdodCA9IDUsIGRwaSA9IDYwMCwgYmcgPSAid2hpdGUiKQoKY2F0KCJcbuKckyBNYW51c2NyaXB0IGZpZ3VyZSBCWSBDRUxMIExJTkUgc2F2ZWQgKFBORywgUERGLCBUSUZGIGF0IDYwMCBEUEkpXG4iKQoKYGBgCiMjICBDT01CSU5FRCBNQU5VU0NSSVBUIEZJR1VSRTogQ2x1c3RlcnMgw5cgQ2VsbCBMaW5lcyAoT3B0aW9uYWwpCmBgYHtyLCBmaWcuaGVpZ2h0PSA1LCBmaWcud2lkdGg9IDQ4IH0KIyBQcmVwYXJlIGRhdGEgYnkgQ0xVU1RFUiDDlyBDRUxMIExJTkUKY29sc19uZWVkZWRfY29tYmluZWQgPC0gYygic2V1cmF0X2NsdXN0ZXJzIiwgImNlbGxfbGluZSIsICJwcmVkaWN0ZWQuaWQiLCAicHJlZGljdGVkLmNlbGx0eXBlLmwyIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgInNpbmdsZXIuaW1tdW5lIiwgInNjQVRPTUlDX2Fubm90YXRpb24iKQoKaGVhdG1hcF9kYXRhX2NvbWJpbmVkIDwtIEFsbF9zYW1wbGVzX01lcmdlZEBtZXRhLmRhdGFbLCBjb2xzX25lZWRlZF9jb21iaW5lZF0gJT4lCiAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzLCBjZWxsX2xpbmUpICU+JQogIHN1bW1hcmlzZSgKICAgc2NQcmVkID0gbmFtZXMoc29ydCh0YWJsZShwcmVkaWN0ZWQuaWQpLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdLAogICAgQXppbXV0aCA9IG5hbWVzKHNvcnQodGFibGUocHJlZGljdGVkLmNlbGx0eXBlLmwyKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIFNpbmdsZVIgPSBuYW1lcyhzb3J0KHRhYmxlKHNpbmdsZXIuaW1tdW5lKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIHNjQVRPTUlDID0gbmFtZXMoc29ydCh0YWJsZShzY0FUT01JQ19hbm5vdGF0aW9uKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXSwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhzY1ByZWQsIEF6aW11dGgsIFNpbmdsZVIsIHNjQVRPTUlDKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiTWV0aG9kIiwgdmFsdWVzX3RvID0gIkNlbGxUeXBlIikgJT4lCiAgbXV0YXRlKAogICAgTWV0aG9kID0gZmFjdG9yKE1ldGhvZCwgbGV2ZWxzID0gYygic2NBVE9NSUMiLCAiU2luZ2xlUiIsICJBemltdXRoIiwgInNjUHJlZCIpKSwKICAgIHNldXJhdF9jbHVzdGVycyA9IGZhY3RvcihzZXVyYXRfY2x1c3RlcnMsIGxldmVscyA9IGFzLmNoYXJhY3RlcigwOjEzKSksCiAgICBjb21iaW5lZF9sYWJlbCA9IHBhc3RlMCgiQyIsIHNldXJhdF9jbHVzdGVycywgIl8iLCBjZWxsX2xpbmUpLAogICAgQ2VsbFR5cGVfZGlzcGxheSA9IGlmZWxzZShuY2hhcihDZWxsVHlwZSkgPiAxNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMChzdWJzdHIoQ2VsbFR5cGUsIDEsIDEyKSwgIi4uLiIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2VsbFR5cGUpCiAgKQoKIyBDcmVhdGUgY29tYmluZWQgZmlndXJlCnBfbWFudXNjcmlwdF9jb21iaW5lZCA8LSBnZ3Bsb3QoaGVhdG1hcF9kYXRhX2NvbWJpbmVkLCAKICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IGNvbWJpbmVkX2xhYmVsLCB5ID0gTWV0aG9kLCBsYWJlbCA9IENlbGxUeXBlX2Rpc3BsYXkpKSArCiAgZ2VvbV90aWxlKGNvbG9yID0gImdyZXkyMCIsIGZpbGwgPSAid2hpdGUiLCBsaW5ld2lkdGggPSAwLjUpICsKICBnZW9tX3RleHQoc2l6ZSA9IDIsIGhqdXN0ID0gMC41LCB2anVzdCA9IDAuNSwgZm9udGZhY2UgPSAiYm9sZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQ2VsbCBUeXBlIEFubm90YXRpb24gLSBCeSBDbHVzdGVyIMOXIENlbGwgTGluZSIsCiAgICB4ID0gIkNsdXN0ZXJfQ2VsbExpbmUiLAogICAgeSA9ICJBbm5vdGF0aW9uIE1ldGhvZCIKICApICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBzaXplID0gOCwgYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCB2anVzdCA9IDAuNSksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBzaXplID0gMTIpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQsIGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE2KSwKICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigxMCwgMTAsIDEwLCAxMCkKICApCgpwcmludChwX21hbnVzY3JpcHRfY29tYmluZWQpCgojIFNhdmUKZ2dzYXZlKCJGaWd1cmVYX0NlbGxMaW5lc19Bbm5vdGF0aW9uX0NMVVNURVJTX3hfQ0VMTExJTkVTLnBuZyIsIAogICAgICAgcGxvdCA9IHBfbWFudXNjcmlwdF9jb21iaW5lZCwgd2lkdGggPSAyNCwgaGVpZ2h0ID0gNiwgZHBpID0gNjAwLCBiZyA9ICJ3aGl0ZSIpCmdnc2F2ZSgiRmlndXJlWF9DZWxsTGluZXNfQW5ub3RhdGlvbl9DTFVTVEVSU194X0NFTExMSU5FUy5wZGYiLCAKICAgICAgIHBsb3QgPSBwX21hbnVzY3JpcHRfY29tYmluZWQsIHdpZHRoID0gMjQsIGhlaWdodCA9IDYpCgpjYXQoIlxu4pyTIENvbWJpbmVkIG1hbnVzY3JpcHQgZmlndXJlIChDbHVzdGVyIMOXIENlbGwgTGluZSkgc2F2ZWRcbiIpCgpgYGAKCgoKCgoKCiMgc2Vzc2lvbkluZm8oKQpgYGB7cn0KCnNlc3Npb25JbmZvKCkKCgpgYGAKCgo=