1 Introduction

This R Markdown script outlines a comprehensive workflow for the analysis of Antibody-Derived Tag (ADT) data from a CITE-seq experiment, integrated with the corresponding single-cell RNA sequencing (scRNA-seq) data, using the Seurat package. The analysis follows the user-specified steps for Quality Control (QC), normalization, dimensionality reduction, marker discovery, and visualization.

Prerequisites: The analysis assumes a Seurat object named All_samples_Merged is loaded, containing both RNA (SCT assay) and ADT (ADT assay) data.

# Replace with the actual path to your Seurat object
All_samples_Merged <- readRDS("../../../PHD_3rd_YEAR_Analysis/0-Seurat_RDS_OBJECT_FINAL/All_samples_Merged_with_Renamed_Clusters_final-26-10-2025.rds") 

# Verify the object structure
print(All_samples_Merged)
DefaultAssay(All_samples_Merged) <- "SCT"

DimPlot(All_samples_Merged, group.by = "orig.ident", label = T)

2 QC & Normalization of ADT

2.1 Examine Raw Counts Distribution and Protein-to-RNA Correlation

This step is crucial for initial quality assessment and identifying potential issues like non-specific binding or technical artifacts.

Idents(All_samples_Merged) <- "orig.ident"


# Extract raw counts from somewhere safe (e.g. backup or original data)
raw_counts <- GetAssayData(All_samples_Merged, assay = "ADT", layer = "counts")

# Create a new ADT assay with the raw counts, resetting normalized/scaled data
All_samples_Merged[["ADT"]] <- CreateAssayObject(counts = raw_counts)

# Set the default assay to ADT
DefaultAssay(All_samples_Merged) <- "ADT"


# Calculate the total number of ADT counts per cell
All_samples_Merged$nFeature_ADT <- colSums(All_samples_Merged@assays$ADT@counts > 0)
All_samples_Merged$nCount_ADT <- colSums(All_samples_Merged@assays$ADT@counts)

# Visualize ADT count distribution
p1 <- VlnPlot(All_samples_Merged, features = c("nFeature_ADT", "nCount_ADT"), ncol = 2, pt.size = 0.1)
print(p1)

# Identify potential outlier antibodies (e.g., highly expressed across all cells)
# Plot raw counts for all 28 proteins
adt_features <- rownames(All_samples_Merged[["ADT"]])
p2 <- VlnPlot(All_samples_Merged, features = adt_features[1:min(28, length(adt_features))], ncol = 4, pt.size = 0)
print(p2)

2.2 Apply Centered Log-Ratio (CLR) Normalization

CLR normalization is the standard method in Seurat for ADT data, treating the protein counts as compositional data.

# margin=1 means “perform CLR normalization for each feature independently” # margin=2 means “perform CLR normalization within a cell”

Alt text

# Apply CLR normalization (margin = 2 normalizes across features for each cell)
All_samples_Merged <- NormalizeData(All_samples_Merged, assay = "ADT", normalization.method = "CLR", margin = 2)

2.3 Quality control after nomrlization

# Identify potential outlier antibodies (e.g., highly expressed across all cells)
# Plot raw counts for all 28 proteins
adt_features <- rownames(All_samples_Merged[["ADT"]])
p3 <- VlnPlot(All_samples_Merged, features = adt_features[1:min(28, length(adt_features))], ncol = 4, pt.size = 0)
print(p3)

2.4 Visualize multiple modalities side-by-side

Idents(All_samples_Merged) <- "cell_line"
# Now, we will visualize CD14 levels for RNA and protein By setting the default assay, we can
# visualize one or the other
DefaultAssay(All_samples_Merged) <- "SCT"
p1 <- FeaturePlot(All_samples_Merged, "CD4", cols = c("lightgrey", "darkgreen")) + ggtitle("CD4 protein")
DefaultAssay(All_samples_Merged) <- "SCT"
p2 <- FeaturePlot(All_samples_Merged, "CD4") + ggtitle("CD4 RNA")

# place plots side-by-side
p1 | p2


Key(All_samples_Merged[["RNA"]])
[1] "rna_"
Key(All_samples_Merged[["SCT"]])
[1] "sct_"
Key(All_samples_Merged[["ADT"]])
[1] "adt_"
# Now, we can include the key in the feature name, which overrides the default assay
p1 <- FeaturePlot(All_samples_Merged, "adt_PD1", cols = c("lightgrey", "darkgreen")) + ggtitle("PD1 protein")
p2 <- FeaturePlot(All_samples_Merged, "rna_PDCD1") + ggtitle("PD1 RNA")
p3 <- FeaturePlot(All_samples_Merged, "sct_PDCD1") + ggtitle("PD1 SCT")
p1 | p2 | p3


# Now, we can include the key in the feature name, which overrides the default assay
p1 <- FeaturePlot(All_samples_Merged, "adt_CD274", cols = c("lightgrey", "darkgreen")) + ggtitle("CD274 protein")
p2 <- FeaturePlot(All_samples_Merged, "rna_CD274") + ggtitle("CD274 RNA")
p3 <- FeaturePlot(All_samples_Merged, "sct_CD274") + ggtitle("CD274 RNA_SCT")
p1 | p2 | p3


# Now, we can include the key in the feature name, which overrides the default assay
p1 <- FeaturePlot(All_samples_Merged, "adt_CD7", cols = c("lightgrey", "darkgreen")) + ggtitle("CD7 protein")
p2 <- FeaturePlot(All_samples_Merged, "rna_CD7") + ggtitle("CD7 RNA")
p3 <- FeaturePlot(All_samples_Merged, "sct_CD7") + ggtitle("CD7 RNA_SCT")
p1 | p2 | p3

DefaultAssay -> "ADT"
Idents(object=All_samples_Merged) <- "orig.ident"
FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD2")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD3")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD5")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD28")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD127")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD45")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD19")


FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD7")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD25")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD26")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD45RA")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD45RO")


FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD62L")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CCR7")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CCR4")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CCR6")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CCR8")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CCR10")


FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CXCR3")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CXCR4")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD95")


FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD30")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD40")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD44")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD45")


FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_CD274")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "adt_PD1")

FeatureScatter(All_samples_Merged, feature1 = "adt_CD4", feature2 = "TCRab")


library(Seurat)
library(dittoSeq)
library(RColorBrewer)

# Make sure default assay is ADT
DefaultAssay(All_samples_Merged) <- "ADT"

# Keep only cells that have ADT data
adt_cells <- colnames(All_samples_Merged[["ADT"]]@data)
All_samples_ADT <- subset(All_samples_Merged, cells = adt_cells)

# Choose a reasonable set of proteins to plot
genes_to_plot <- rownames(All_samples_ADT[["ADT"]])
# Optional: remove proteins with 0 expression across all cells
genes_to_plot <- genes_to_plot[rowSums(All_samples_ADT[["ADT"]]@data) > 0]

# Custom color palette
my_colors <- colorRampPalette(brewer.pal(9, "YlGnBu"))(100)

# Run dittoHeatmap
dittoHeatmap(
  All_samples_ADT,
  genes = genes_to_plot,
  annot.by = "Patient_origin",      # or "orig.ident"
  heatmap.colors = my_colors,
  scaled.to.max = TRUE
)


rownames(All_samples_Merged[["ADT"]])

DefaultAssay(All_samples_Merged) <- "ADT"



library(dittoSeq)

dittoHeatmap(All_samples_Merged, genes,
    annot.by = c("orig.ident"))

# Load additional libraries for color palettes
library(RColorBrewer)

# Define a custom color palette for the heatmap
my_colors <- colorRampPalette(brewer.pal(9, "YlGnBu"))(100)  # Example: Yellow to Blue

# Generate the heatmap with custom colors
dittoHeatmap(
  All_samples_Merged, 
  genes,
  annot.by = c("orig.ident"),
  heatmap.colors = my_colors,  # Apply custom heatmap colors
  scaled.to.max = TRUE         # Optionally, scale data for better contrast
)

DefaultAssay(All_samples_Merged) <- 'ADT'

# Get cluster information
cluster_info <- All_samples_Merged$seurat_clusters
print(table(cluster_info))  # This will show the distribution of cells across clusters

# Get unique cluster IDs
clusters <- sort(unique(cluster_info))
print(clusters)  # This will show you what cluster IDs are actually present

# Calculate mean expression for each protein in each cluster
mean_expression_clusters <- sapply(clusters, function(cl) {
  cells <- names(cluster_info)[cluster_info == cl]
  rowMeans(LayerData(All_samples_Merged, assay = "ADT", layer = "data")[, cells, drop = FALSE])
})

# Check the dimensions of the resulting matrix
print(dim(mean_expression_clusters))

# Create cluster labels starting from 0
cluster_labels <- paste0("Cluster ", seq(0, length(clusters) - 1))

# Set column names
colnames(mean_expression_clusters) <- cluster_labels

# Create the heatmap
pheatmap(mean_expression_clusters, 
         main = "Protein Expression Heatmap by Cluster",
         scale = "row",
         cluster_rows = TRUE, 
         cluster_cols = TRUE,
         show_rownames = TRUE,
         show_colnames = TRUE,
         fontsize_col = 8,
         angle_col = 45)

# For the tree visualization
dist_matrix_clusters <- dist(t(mean_expression_clusters))
tree_clusters <- nj(dist_matrix_clusters)

plot(tree_clusters, main = "Cluster Similarity Tree Based on Protein Expression")
LS0tCnRpdGxlOiAiRnVsbCBBRFQgKENJVEUtc2VxKSBBbmFseXNpcyBXb3JrZmxvdy0xNy0xMS0yMDI1LU1hcmdpbjIiCmF1dGhvcjogIk5BU0lSIE1BSE1PT0QgQUJCQVNJIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy5EYXRlKCksICclQiAlZCwgJVknKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiB0cnVlCiAgICB0aGVtZTogam91cm5hbAotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDgpCiMgTG9hZCBuZWNlc3NhcnkgbGlicmFyaWVzCmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KGRwbHlyKQoKYGBgCgojIEludHJvZHVjdGlvbgpUaGlzIFIgTWFya2Rvd24gc2NyaXB0IG91dGxpbmVzIGEgY29tcHJlaGVuc2l2ZSB3b3JrZmxvdyBmb3IgdGhlIGFuYWx5c2lzIG9mIEFudGlib2R5LURlcml2ZWQgVGFnIChBRFQpIGRhdGEgZnJvbSBhIENJVEUtc2VxIGV4cGVyaW1lbnQsIGludGVncmF0ZWQgd2l0aCB0aGUgY29ycmVzcG9uZGluZyBzaW5nbGUtY2VsbCBSTkEgc2VxdWVuY2luZyAoc2NSTkEtc2VxKSBkYXRhLCB1c2luZyB0aGUgU2V1cmF0IHBhY2thZ2UuIFRoZSBhbmFseXNpcyBmb2xsb3dzIHRoZSB1c2VyLXNwZWNpZmllZCBzdGVwcyBmb3IgUXVhbGl0eSBDb250cm9sIChRQyksIG5vcm1hbGl6YXRpb24sIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiwgbWFya2VyIGRpc2NvdmVyeSwgYW5kIHZpc3VhbGl6YXRpb24uCgoqKlByZXJlcXVpc2l0ZXM6KiogVGhlIGFuYWx5c2lzIGFzc3VtZXMgYSBTZXVyYXQgb2JqZWN0IG5hbWVkIGBBbGxfc2FtcGxlc19NZXJnZWRgIGlzIGxvYWRlZCwgY29udGFpbmluZyBib3RoIFJOQSAoU0NUIGFzc2F5KSBhbmQgQURUIChBRFQgYXNzYXkpIGRhdGEuCgpgYGB7ciBsb2FkX2RhdGEsIGV2YWw9RkFMU0V9CiMgUmVwbGFjZSB3aXRoIHRoZSBhY3R1YWwgcGF0aCB0byB5b3VyIFNldXJhdCBvYmplY3QKQWxsX3NhbXBsZXNfTWVyZ2VkIDwtIHJlYWRSRFMoIi4uLy4uLy4uL1BIRF8zcmRfWUVBUl9BbmFseXNpcy8wLVNldXJhdF9SRFNfT0JKRUNUX0ZJTkFML0FsbF9zYW1wbGVzX01lcmdlZF93aXRoX1JlbmFtZWRfQ2x1c3RlcnNfZmluYWwtMjYtMTAtMjAyNS5yZHMiKSAKCiMgVmVyaWZ5IHRoZSBvYmplY3Qgc3RydWN0dXJlCnByaW50KEFsbF9zYW1wbGVzX01lcmdlZCkKRGVmYXVsdEFzc2F5KEFsbF9zYW1wbGVzX01lcmdlZCkgPC0gIlNDVCIKCkRpbVBsb3QoQWxsX3NhbXBsZXNfTWVyZ2VkLCBncm91cC5ieSA9ICJvcmlnLmlkZW50IiwgbGFiZWwgPSBUKQpgYGAKCiMgIFFDICYgTm9ybWFsaXphdGlvbiBvZiBBRFQKCiMjIEV4YW1pbmUgUmF3IENvdW50cyBEaXN0cmlidXRpb24gYW5kIFByb3RlaW4tdG8tUk5BIENvcnJlbGF0aW9uCgpUaGlzIHN0ZXAgaXMgY3J1Y2lhbCBmb3IgaW5pdGlhbCBxdWFsaXR5IGFzc2Vzc21lbnQgYW5kIGlkZW50aWZ5aW5nIHBvdGVudGlhbCBpc3N1ZXMgbGlrZSBub24tc3BlY2lmaWMgYmluZGluZyBvciB0ZWNobmljYWwgYXJ0aWZhY3RzLgoKYGBge3IgYWR0X3FjMSwgZmlnLmhlaWdodD0gNCwgZmlnLndpZHRoPSAxMn0KSWRlbnRzKEFsbF9zYW1wbGVzX01lcmdlZCkgPC0gIm9yaWcuaWRlbnQiCgoKIyBFeHRyYWN0IHJhdyBjb3VudHMgZnJvbSBzb21ld2hlcmUgc2FmZSAoZS5nLiBiYWNrdXAgb3Igb3JpZ2luYWwgZGF0YSkKcmF3X2NvdW50cyA8LSBHZXRBc3NheURhdGEoQWxsX3NhbXBsZXNfTWVyZ2VkLCBhc3NheSA9ICJBRFQiLCBsYXllciA9ICJjb3VudHMiKQoKIyBDcmVhdGUgYSBuZXcgQURUIGFzc2F5IHdpdGggdGhlIHJhdyBjb3VudHMsIHJlc2V0dGluZyBub3JtYWxpemVkL3NjYWxlZCBkYXRhCkFsbF9zYW1wbGVzX01lcmdlZFtbIkFEVCJdXSA8LSBDcmVhdGVBc3NheU9iamVjdChjb3VudHMgPSByYXdfY291bnRzKQoKIyBTZXQgdGhlIGRlZmF1bHQgYXNzYXkgdG8gQURUCkRlZmF1bHRBc3NheShBbGxfc2FtcGxlc19NZXJnZWQpIDwtICJBRFQiCgoKIyBDYWxjdWxhdGUgdGhlIHRvdGFsIG51bWJlciBvZiBBRFQgY291bnRzIHBlciBjZWxsCkFsbF9zYW1wbGVzX01lcmdlZCRuRmVhdHVyZV9BRFQgPC0gY29sU3VtcyhBbGxfc2FtcGxlc19NZXJnZWRAYXNzYXlzJEFEVEBjb3VudHMgPiAwKQpBbGxfc2FtcGxlc19NZXJnZWQkbkNvdW50X0FEVCA8LSBjb2xTdW1zKEFsbF9zYW1wbGVzX01lcmdlZEBhc3NheXMkQURUQGNvdW50cykKCiMgVmlzdWFsaXplIEFEVCBjb3VudCBkaXN0cmlidXRpb24KcDEgPC0gVmxuUGxvdChBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmVzID0gYygibkZlYXR1cmVfQURUIiwgIm5Db3VudF9BRFQiKSwgbmNvbCA9IDIsIHB0LnNpemUgPSAwLjEpCnByaW50KHAxKQpgYGAKCmBgYHtyIGFkdF9xYzIsIGZpZy5oZWlnaHQ9IDI0LCBmaWcud2lkdGg9IDMwfQojIElkZW50aWZ5IHBvdGVudGlhbCBvdXRsaWVyIGFudGlib2RpZXMgKGUuZy4sIGhpZ2hseSBleHByZXNzZWQgYWNyb3NzIGFsbCBjZWxscykKIyBQbG90IHJhdyBjb3VudHMgZm9yIGFsbCAyOCBwcm90ZWlucwphZHRfZmVhdHVyZXMgPC0gcm93bmFtZXMoQWxsX3NhbXBsZXNfTWVyZ2VkW1siQURUIl1dKQpwMiA8LSBWbG5QbG90KEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZXMgPSBhZHRfZmVhdHVyZXNbMTptaW4oMjgsIGxlbmd0aChhZHRfZmVhdHVyZXMpKV0sIG5jb2wgPSA0LCBwdC5zaXplID0gMCkKcHJpbnQocDIpCgpgYGAKCiMjICBBcHBseSBDZW50ZXJlZCBMb2ctUmF0aW8gKENMUikgTm9ybWFsaXphdGlvbgoKQ0xSIG5vcm1hbGl6YXRpb24gaXMgdGhlIHN0YW5kYXJkIG1ldGhvZCBpbiBTZXVyYXQgZm9yIEFEVCBkYXRhLCB0cmVhdGluZyB0aGUgcHJvdGVpbiBjb3VudHMgYXMgY29tcG9zaXRpb25hbCBkYXRhLgoKICMgbWFyZ2luPTEgbWVhbnMgInBlcmZvcm0gQ0xSIG5vcm1hbGl6YXRpb24gZm9yIGVhY2ggZmVhdHVyZSBpbmRlcGVuZGVudGx5IgogICAgIyBtYXJnaW49MiBtZWFucyAicGVyZm9ybSBDTFIgbm9ybWFsaXphdGlvbiB3aXRoaW4gYSBjZWxsIgoKPGltZyBzcmM9ImFkdF9tYXJnaW4ucG5nIiBhbHQ9IkFsdCB0ZXh0IiB3aWR0aD0iNTAwIiBoZWlnaHQ9IjEwMCI+CgoKYGBge3IgYWR0X25vcm1hbGl6YXRpb24sIGV2YWw9RkFMU0V9CiMgQXBwbHkgQ0xSIG5vcm1hbGl6YXRpb24gKG1hcmdpbiA9IDIgbm9ybWFsaXplcyBhY3Jvc3MgZmVhdHVyZXMgZm9yIGVhY2ggY2VsbCkKQWxsX3NhbXBsZXNfTWVyZ2VkIDwtIE5vcm1hbGl6ZURhdGEoQWxsX3NhbXBsZXNfTWVyZ2VkLCBhc3NheSA9ICJBRFQiLCBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJDTFIiLCBtYXJnaW4gPSAyKQpgYGAKCiMjIFF1YWxpdHkgY29udHJvbCBhZnRlciBub21ybGl6YXRpb24KYGBge3IgYWR0X3FjMywgZmlnLmhlaWdodD0gMjQsIGZpZy53aWR0aD0gMzB9CiMgSWRlbnRpZnkgcG90ZW50aWFsIG91dGxpZXIgYW50aWJvZGllcyAoZS5nLiwgaGlnaGx5IGV4cHJlc3NlZCBhY3Jvc3MgYWxsIGNlbGxzKQojIFBsb3QgcmF3IGNvdW50cyBmb3IgYWxsIDI4IHByb3RlaW5zCmFkdF9mZWF0dXJlcyA8LSByb3duYW1lcyhBbGxfc2FtcGxlc19NZXJnZWRbWyJBRFQiXV0pCnAzIDwtIFZsblBsb3QoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlcyA9IGFkdF9mZWF0dXJlc1sxOm1pbigyOCwgbGVuZ3RoKGFkdF9mZWF0dXJlcykpXSwgbmNvbCA9IDQsIHB0LnNpemUgPSAwKQpwcmludChwMykKYGBgCgojIyBWaXN1YWxpemUgbXVsdGlwbGUgbW9kYWxpdGllcyBzaWRlLWJ5LXNpZGUKYGBge3IgLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD0xMH0KSWRlbnRzKEFsbF9zYW1wbGVzX01lcmdlZCkgPC0gImNlbGxfbGluZSIKIyBOb3csIHdlIHdpbGwgdmlzdWFsaXplIENEMTQgbGV2ZWxzIGZvciBSTkEgYW5kIHByb3RlaW4gQnkgc2V0dGluZyB0aGUgZGVmYXVsdCBhc3NheSwgd2UgY2FuCiMgdmlzdWFsaXplIG9uZSBvciB0aGUgb3RoZXIKRGVmYXVsdEFzc2F5KEFsbF9zYW1wbGVzX01lcmdlZCkgPC0gIlNDVCIKcDEgPC0gRmVhdHVyZVBsb3QoQWxsX3NhbXBsZXNfTWVyZ2VkLCAiQ0Q0IiwgY29scyA9IGMoImxpZ2h0Z3JleSIsICJkYXJrZ3JlZW4iKSkgKyBnZ3RpdGxlKCJDRDQgcHJvdGVpbiIpCkRlZmF1bHRBc3NheShBbGxfc2FtcGxlc19NZXJnZWQpIDwtICJTQ1QiCnAyIDwtIEZlYXR1cmVQbG90KEFsbF9zYW1wbGVzX01lcmdlZCwgIkNENCIpICsgZ2d0aXRsZSgiQ0Q0IFJOQSIpCgojIHBsYWNlIHBsb3RzIHNpZGUtYnktc2lkZQpwMSB8IHAyCgpLZXkoQWxsX3NhbXBsZXNfTWVyZ2VkW1siUk5BIl1dKQoKS2V5KEFsbF9zYW1wbGVzX01lcmdlZFtbIlNDVCJdXSkKCktleShBbGxfc2FtcGxlc19NZXJnZWRbWyJBRFQiXV0pCgoKIyBOb3csIHdlIGNhbiBpbmNsdWRlIHRoZSBrZXkgaW4gdGhlIGZlYXR1cmUgbmFtZSwgd2hpY2ggb3ZlcnJpZGVzIHRoZSBkZWZhdWx0IGFzc2F5CnAxIDwtIEZlYXR1cmVQbG90KEFsbF9zYW1wbGVzX01lcmdlZCwgImFkdF9QRDEiLCBjb2xzID0gYygibGlnaHRncmV5IiwgImRhcmtncmVlbiIpKSArIGdndGl0bGUoIlBEMSBwcm90ZWluIikKcDIgPC0gRmVhdHVyZVBsb3QoQWxsX3NhbXBsZXNfTWVyZ2VkLCAicm5hX1BEQ0QxIikgKyBnZ3RpdGxlKCJQRDEgUk5BIikKcDMgPC0gRmVhdHVyZVBsb3QoQWxsX3NhbXBsZXNfTWVyZ2VkLCAic2N0X1BEQ0QxIikgKyBnZ3RpdGxlKCJQRDEgU0NUIikKcDEgfCBwMiB8IHAzCgojIE5vdywgd2UgY2FuIGluY2x1ZGUgdGhlIGtleSBpbiB0aGUgZmVhdHVyZSBuYW1lLCB3aGljaCBvdmVycmlkZXMgdGhlIGRlZmF1bHQgYXNzYXkKcDEgPC0gRmVhdHVyZVBsb3QoQWxsX3NhbXBsZXNfTWVyZ2VkLCAiYWR0X0NEMjc0IiwgY29scyA9IGMoImxpZ2h0Z3JleSIsICJkYXJrZ3JlZW4iKSkgKyBnZ3RpdGxlKCJDRDI3NCBwcm90ZWluIikKcDIgPC0gRmVhdHVyZVBsb3QoQWxsX3NhbXBsZXNfTWVyZ2VkLCAicm5hX0NEMjc0IikgKyBnZ3RpdGxlKCJDRDI3NCBSTkEiKQpwMyA8LSBGZWF0dXJlUGxvdChBbGxfc2FtcGxlc19NZXJnZWQsICJzY3RfQ0QyNzQiKSArIGdndGl0bGUoIkNEMjc0IFJOQV9TQ1QiKQpwMSB8IHAyIHwgcDMKCiMgTm93LCB3ZSBjYW4gaW5jbHVkZSB0aGUga2V5IGluIHRoZSBmZWF0dXJlIG5hbWUsIHdoaWNoIG92ZXJyaWRlcyB0aGUgZGVmYXVsdCBhc3NheQpwMSA8LSBGZWF0dXJlUGxvdChBbGxfc2FtcGxlc19NZXJnZWQsICJhZHRfQ0Q3IiwgY29scyA9IGMoImxpZ2h0Z3JleSIsICJkYXJrZ3JlZW4iKSkgKyBnZ3RpdGxlKCJDRDcgcHJvdGVpbiIpCnAyIDwtIEZlYXR1cmVQbG90KEFsbF9zYW1wbGVzX01lcmdlZCwgInJuYV9DRDciKSArIGdndGl0bGUoIkNENyBSTkEiKQpwMyA8LSBGZWF0dXJlUGxvdChBbGxfc2FtcGxlc19NZXJnZWQsICJzY3RfQ0Q3IikgKyBnZ3RpdGxlKCJDRDcgUk5BX1NDVCIpCnAxIHwgcDIgfCBwMwpgYGAKCmBgYHtyICwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTB9CkRlZmF1bHRBc3NheSAtPiAiQURUIgpJZGVudHMob2JqZWN0PUFsbF9zYW1wbGVzX01lcmdlZCkgPC0gIm9yaWcuaWRlbnQiCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DRDIiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0QzIikKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiYWR0X0NENSIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DRDI4IikKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiYWR0X0NEMTI3IikKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiYWR0X0NENDUiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0QxOSIpCgpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0Q3IikKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiYWR0X0NEMjUiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0QyNiIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DRDQ1UkEiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0Q0NVJPIikKCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DRDYyTCIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DQ1I3IikKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiYWR0X0NDUjQiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0NSNiIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DQ1I4IikKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiYWR0X0NDUjEwIikKCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DWENSMyIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DWENSNCIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DRDk1IikKCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DRDMwIikKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiYWR0X0NENDAiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0Q0NCIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DRDQ1IikKCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DRDI3NCIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9QRDEiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJUQ1JhYiIpCmBgYAoKYGBge3IgQURULCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMn0KCmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KGRpdHRvU2VxKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKCiMgTWFrZSBzdXJlIGRlZmF1bHQgYXNzYXkgaXMgQURUCkRlZmF1bHRBc3NheShBbGxfc2FtcGxlc19NZXJnZWQpIDwtICJBRFQiCgojIEtlZXAgb25seSBjZWxscyB0aGF0IGhhdmUgQURUIGRhdGEKYWR0X2NlbGxzIDwtIGNvbG5hbWVzKEFsbF9zYW1wbGVzX01lcmdlZFtbIkFEVCJdXUBkYXRhKQpBbGxfc2FtcGxlc19BRFQgPC0gc3Vic2V0KEFsbF9zYW1wbGVzX01lcmdlZCwgY2VsbHMgPSBhZHRfY2VsbHMpCgojIENob29zZSBhIHJlYXNvbmFibGUgc2V0IG9mIHByb3RlaW5zIHRvIHBsb3QKZ2VuZXNfdG9fcGxvdCA8LSByb3duYW1lcyhBbGxfc2FtcGxlc19BRFRbWyJBRFQiXV0pCiMgT3B0aW9uYWw6IHJlbW92ZSBwcm90ZWlucyB3aXRoIDAgZXhwcmVzc2lvbiBhY3Jvc3MgYWxsIGNlbGxzCmdlbmVzX3RvX3Bsb3QgPC0gZ2VuZXNfdG9fcGxvdFtyb3dTdW1zKEFsbF9zYW1wbGVzX0FEVFtbIkFEVCJdXUBkYXRhKSA+IDBdCgojIEN1c3RvbSBjb2xvciBwYWxldHRlCm15X2NvbG9ycyA8LSBjb2xvclJhbXBQYWxldHRlKGJyZXdlci5wYWwoOSwgIllsR25CdSIpKSgxMDApCgojIFJ1biBkaXR0b0hlYXRtYXAKZGl0dG9IZWF0bWFwKAogIEFsbF9zYW1wbGVzX0FEVCwKICBnZW5lcyA9IGdlbmVzX3RvX3Bsb3QsCiAgYW5ub3QuYnkgPSAiUGF0aWVudF9vcmlnaW4iLCAgICAgICMgb3IgIm9yaWcuaWRlbnQiCiAgaGVhdG1hcC5jb2xvcnMgPSBteV9jb2xvcnMsCiAgc2NhbGVkLnRvLm1heCA9IFRSVUUKKQoKCmBgYAoKCmBgYHtyIEFEVDIsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEyfQoKCnJvd25hbWVzKEFsbF9zYW1wbGVzX01lcmdlZFtbIkFEVCJdXSkKCkRlZmF1bHRBc3NheShBbGxfc2FtcGxlc19NZXJnZWQpIDwtICJBRFQiCgoKCmxpYnJhcnkoZGl0dG9TZXEpCgpkaXR0b0hlYXRtYXAoQWxsX3NhbXBsZXNfTWVyZ2VkLCBnZW5lcywKICAgIGFubm90LmJ5ID0gYygib3JpZy5pZGVudCIpKQoKIyBMb2FkIGFkZGl0aW9uYWwgbGlicmFyaWVzIGZvciBjb2xvciBwYWxldHRlcwpsaWJyYXJ5KFJDb2xvckJyZXdlcikKCiMgRGVmaW5lIGEgY3VzdG9tIGNvbG9yIHBhbGV0dGUgZm9yIHRoZSBoZWF0bWFwCm15X2NvbG9ycyA8LSBjb2xvclJhbXBQYWxldHRlKGJyZXdlci5wYWwoOSwgIllsR25CdSIpKSgxMDApICAjIEV4YW1wbGU6IFllbGxvdyB0byBCbHVlCgojIEdlbmVyYXRlIHRoZSBoZWF0bWFwIHdpdGggY3VzdG9tIGNvbG9ycwpkaXR0b0hlYXRtYXAoCiAgQWxsX3NhbXBsZXNfTWVyZ2VkLCAKICBnZW5lcywKICBhbm5vdC5ieSA9IGMoIm9yaWcuaWRlbnQiKSwKICBoZWF0bWFwLmNvbG9ycyA9IG15X2NvbG9ycywgICMgQXBwbHkgY3VzdG9tIGhlYXRtYXAgY29sb3JzCiAgc2NhbGVkLnRvLm1heCA9IFRSVUUgICAgICAgICAjIE9wdGlvbmFsbHksIHNjYWxlIGRhdGEgZm9yIGJldHRlciBjb250cmFzdAopCgpgYGAKCgoKCmBgYHtyIGRhdGEzLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMH0KCkRlZmF1bHRBc3NheShBbGxfc2FtcGxlc19NZXJnZWQpIDwtICdBRFQnCgojIEdldCBjbHVzdGVyIGluZm9ybWF0aW9uCmNsdXN0ZXJfaW5mbyA8LSBBbGxfc2FtcGxlc19NZXJnZWQkc2V1cmF0X2NsdXN0ZXJzCnByaW50KHRhYmxlKGNsdXN0ZXJfaW5mbykpICAjIFRoaXMgd2lsbCBzaG93IHRoZSBkaXN0cmlidXRpb24gb2YgY2VsbHMgYWNyb3NzIGNsdXN0ZXJzCgojIEdldCB1bmlxdWUgY2x1c3RlciBJRHMKY2x1c3RlcnMgPC0gc29ydCh1bmlxdWUoY2x1c3Rlcl9pbmZvKSkKcHJpbnQoY2x1c3RlcnMpICAjIFRoaXMgd2lsbCBzaG93IHlvdSB3aGF0IGNsdXN0ZXIgSURzIGFyZSBhY3R1YWxseSBwcmVzZW50CgojIENhbGN1bGF0ZSBtZWFuIGV4cHJlc3Npb24gZm9yIGVhY2ggcHJvdGVpbiBpbiBlYWNoIGNsdXN0ZXIKbWVhbl9leHByZXNzaW9uX2NsdXN0ZXJzIDwtIHNhcHBseShjbHVzdGVycywgZnVuY3Rpb24oY2wpIHsKICBjZWxscyA8LSBuYW1lcyhjbHVzdGVyX2luZm8pW2NsdXN0ZXJfaW5mbyA9PSBjbF0KICByb3dNZWFucyhMYXllckRhdGEoQWxsX3NhbXBsZXNfTWVyZ2VkLCBhc3NheSA9ICJBRFQiLCBsYXllciA9ICJkYXRhIilbLCBjZWxscywgZHJvcCA9IEZBTFNFXSkKfSkKCiMgQ2hlY2sgdGhlIGRpbWVuc2lvbnMgb2YgdGhlIHJlc3VsdGluZyBtYXRyaXgKcHJpbnQoZGltKG1lYW5fZXhwcmVzc2lvbl9jbHVzdGVycykpCgojIENyZWF0ZSBjbHVzdGVyIGxhYmVscyBzdGFydGluZyBmcm9tIDAKY2x1c3Rlcl9sYWJlbHMgPC0gcGFzdGUwKCJDbHVzdGVyICIsIHNlcSgwLCBsZW5ndGgoY2x1c3RlcnMpIC0gMSkpCgojIFNldCBjb2x1bW4gbmFtZXMKY29sbmFtZXMobWVhbl9leHByZXNzaW9uX2NsdXN0ZXJzKSA8LSBjbHVzdGVyX2xhYmVscwoKIyBDcmVhdGUgdGhlIGhlYXRtYXAKcGhlYXRtYXAobWVhbl9leHByZXNzaW9uX2NsdXN0ZXJzLCAKICAgICAgICAgbWFpbiA9ICJQcm90ZWluIEV4cHJlc3Npb24gSGVhdG1hcCBieSBDbHVzdGVyIiwKICAgICAgICAgc2NhbGUgPSAicm93IiwKICAgICAgICAgY2x1c3Rlcl9yb3dzID0gVFJVRSwgCiAgICAgICAgIGNsdXN0ZXJfY29scyA9IFRSVUUsCiAgICAgICAgIHNob3dfcm93bmFtZXMgPSBUUlVFLAogICAgICAgICBzaG93X2NvbG5hbWVzID0gVFJVRSwKICAgICAgICAgZm9udHNpemVfY29sID0gOCwKICAgICAgICAgYW5nbGVfY29sID0gNDUpCgojIEZvciB0aGUgdHJlZSB2aXN1YWxpemF0aW9uCmRpc3RfbWF0cml4X2NsdXN0ZXJzIDwtIGRpc3QodChtZWFuX2V4cHJlc3Npb25fY2x1c3RlcnMpKQp0cmVlX2NsdXN0ZXJzIDwtIG5qKGRpc3RfbWF0cml4X2NsdXN0ZXJzKQoKcGxvdCh0cmVlX2NsdXN0ZXJzLCBtYWluID0gIkNsdXN0ZXIgU2ltaWxhcml0eSBUcmVlIEJhc2VkIG9uIFByb3RlaW4gRXhwcmVzc2lvbiIpCgpgYGAK