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 = 1)

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
)

NA
NA


rownames(All_samples_Merged[["ADT"]])
 [1] "CD274"  "CD30"   "CD40"   "CD3"    "CD45RA" "CD7"    "CCR4"   "CD4"    "CD25"   "CD45RO" "PD1"    "CD44"   "CD5"    "CXCR3"  "CCR6"   "CD62L"  "CCR7"   "CD95"  
[19] "TCRab"  "CXCR4"  "CD2"    "CD28"   "CD127"  "CD45"   "CD26"   "CCR10"  "CCR8"   "CD19"  
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
cluster_info
   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 
# Get unique cluster IDs
clusters <- sort(unique(cluster_info))
print(clusters)  # This will show you what cluster IDs are actually present
 [1] 0  1  2  3  4  5  6  7  8  9  10 11 12 13
Levels: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
# 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))
[1] 28 14
# 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))
Found more than one class "dist" in cache; using the first, from namespace 'spam'
Also defined by ‘BiocGenerics’
tree_clusters <- nj(dist_matrix_clusters)

plot(tree_clusters, main = "Cluster Similarity Tree Based on Protein Expression")

LS0tCnRpdGxlOiAiRnVsbCBBRFQgKENJVEUtc2VxKSBBbmFseXNpcyBXb3JrZmxvdy0xNy0xMS0yMDI1LU1hcmdpbjEiCmF1dGhvcjogIk5BU0lSIE1BSE1PT0QgQUJCQVNJIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy5EYXRlKCksICclQiAlZCwgJVknKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA4KQojIExvYWQgbmVjZXNzYXJ5IGxpYnJhcmllcwpsaWJyYXJ5KFNldXJhdCkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShkcGx5cikKCmBgYAoKIyBJbnRyb2R1Y3Rpb24KVGhpcyBSIE1hcmtkb3duIHNjcmlwdCBvdXRsaW5lcyBhIGNvbXByZWhlbnNpdmUgd29ya2Zsb3cgZm9yIHRoZSBhbmFseXNpcyBvZiBBbnRpYm9keS1EZXJpdmVkIFRhZyAoQURUKSBkYXRhIGZyb20gYSBDSVRFLXNlcSBleHBlcmltZW50LCBpbnRlZ3JhdGVkIHdpdGggdGhlIGNvcnJlc3BvbmRpbmcgc2luZ2xlLWNlbGwgUk5BIHNlcXVlbmNpbmcgKHNjUk5BLXNlcSkgZGF0YSwgdXNpbmcgdGhlIFNldXJhdCBwYWNrYWdlLiBUaGUgYW5hbHlzaXMgZm9sbG93cyB0aGUgdXNlci1zcGVjaWZpZWQgc3RlcHMgZm9yIFF1YWxpdHkgQ29udHJvbCAoUUMpLCBub3JtYWxpemF0aW9uLCBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24sIG1hcmtlciBkaXNjb3ZlcnksIGFuZCB2aXN1YWxpemF0aW9uLgoKKipQcmVyZXF1aXNpdGVzOioqIFRoZSBhbmFseXNpcyBhc3N1bWVzIGEgU2V1cmF0IG9iamVjdCBuYW1lZCBgQWxsX3NhbXBsZXNfTWVyZ2VkYCBpcyBsb2FkZWQsIGNvbnRhaW5pbmcgYm90aCBSTkEgKFNDVCBhc3NheSkgYW5kIEFEVCAoQURUIGFzc2F5KSBkYXRhLgoKYGBge3IgbG9hZF9kYXRhLCBldmFsPUZBTFNFfQojIFJlcGxhY2Ugd2l0aCB0aGUgYWN0dWFsIHBhdGggdG8geW91ciBTZXVyYXQgb2JqZWN0CkFsbF9zYW1wbGVzX01lcmdlZCA8LSByZWFkUkRTKCIuLi8uLi8uLi9QSERfM3JkX1lFQVJfQW5hbHlzaXMvMC1TZXVyYXRfUkRTX09CSkVDVF9GSU5BTC9BbGxfc2FtcGxlc19NZXJnZWRfd2l0aF9SZW5hbWVkX0NsdXN0ZXJzX2ZpbmFsLTI2LTEwLTIwMjUucmRzIikgCgojIFZlcmlmeSB0aGUgb2JqZWN0IHN0cnVjdHVyZQpwcmludChBbGxfc2FtcGxlc19NZXJnZWQpCkRlZmF1bHRBc3NheShBbGxfc2FtcGxlc19NZXJnZWQpIDwtICJTQ1QiCgpEaW1QbG90KEFsbF9zYW1wbGVzX01lcmdlZCwgZ3JvdXAuYnkgPSAib3JpZy5pZGVudCIsIGxhYmVsID0gVCkKYGBgCgojICBRQyAmIE5vcm1hbGl6YXRpb24gb2YgQURUCgojIyBFeGFtaW5lIFJhdyBDb3VudHMgRGlzdHJpYnV0aW9uIGFuZCBQcm90ZWluLXRvLVJOQSBDb3JyZWxhdGlvbgoKVGhpcyBzdGVwIGlzIGNydWNpYWwgZm9yIGluaXRpYWwgcXVhbGl0eSBhc3Nlc3NtZW50IGFuZCBpZGVudGlmeWluZyBwb3RlbnRpYWwgaXNzdWVzIGxpa2Ugbm9uLXNwZWNpZmljIGJpbmRpbmcgb3IgdGVjaG5pY2FsIGFydGlmYWN0cy4KCmBgYHtyIGFkdF9xYzEsIGZpZy5oZWlnaHQ9IDQsIGZpZy53aWR0aD0gMTJ9CklkZW50cyhBbGxfc2FtcGxlc19NZXJnZWQpIDwtICJvcmlnLmlkZW50IgoKCiMgRXh0cmFjdCByYXcgY291bnRzIGZyb20gc29tZXdoZXJlIHNhZmUgKGUuZy4gYmFja3VwIG9yIG9yaWdpbmFsIGRhdGEpCnJhd19jb3VudHMgPC0gR2V0QXNzYXlEYXRhKEFsbF9zYW1wbGVzX01lcmdlZCwgYXNzYXkgPSAiQURUIiwgbGF5ZXIgPSAiY291bnRzIikKCiMgQ3JlYXRlIGEgbmV3IEFEVCBhc3NheSB3aXRoIHRoZSByYXcgY291bnRzLCByZXNldHRpbmcgbm9ybWFsaXplZC9zY2FsZWQgZGF0YQpBbGxfc2FtcGxlc19NZXJnZWRbWyJBRFQiXV0gPC0gQ3JlYXRlQXNzYXlPYmplY3QoY291bnRzID0gcmF3X2NvdW50cykKCiMgU2V0IHRoZSBkZWZhdWx0IGFzc2F5IHRvIEFEVApEZWZhdWx0QXNzYXkoQWxsX3NhbXBsZXNfTWVyZ2VkKSA8LSAiQURUIgoKCiMgQ2FsY3VsYXRlIHRoZSB0b3RhbCBudW1iZXIgb2YgQURUIGNvdW50cyBwZXIgY2VsbApBbGxfc2FtcGxlc19NZXJnZWQkbkZlYXR1cmVfQURUIDwtIGNvbFN1bXMoQWxsX3NhbXBsZXNfTWVyZ2VkQGFzc2F5cyRBRFRAY291bnRzID4gMCkKQWxsX3NhbXBsZXNfTWVyZ2VkJG5Db3VudF9BRFQgPC0gY29sU3VtcyhBbGxfc2FtcGxlc19NZXJnZWRAYXNzYXlzJEFEVEBjb3VudHMpCgojIFZpc3VhbGl6ZSBBRFQgY291bnQgZGlzdHJpYnV0aW9uCnAxIDwtIFZsblBsb3QoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlcyA9IGMoIm5GZWF0dXJlX0FEVCIsICJuQ291bnRfQURUIiksIG5jb2wgPSAyLCBwdC5zaXplID0gMC4xKQpwcmludChwMSkKYGBgCgpgYGB7ciBhZHRfcWMyLCBmaWcuaGVpZ2h0PSAyNCwgZmlnLndpZHRoPSAzMH0KIyBJZGVudGlmeSBwb3RlbnRpYWwgb3V0bGllciBhbnRpYm9kaWVzIChlLmcuLCBoaWdobHkgZXhwcmVzc2VkIGFjcm9zcyBhbGwgY2VsbHMpCiMgUGxvdCByYXcgY291bnRzIGZvciBhbGwgMjggcHJvdGVpbnMKYWR0X2ZlYXR1cmVzIDwtIHJvd25hbWVzKEFsbF9zYW1wbGVzX01lcmdlZFtbIkFEVCJdXSkKcDIgPC0gVmxuUGxvdChBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmVzID0gYWR0X2ZlYXR1cmVzWzE6bWluKDI4LCBsZW5ndGgoYWR0X2ZlYXR1cmVzKSldLCBuY29sID0gNCwgcHQuc2l6ZSA9IDApCnByaW50KHAyKQoKYGBgCgojIyAgQXBwbHkgQ2VudGVyZWQgTG9nLVJhdGlvIChDTFIpIE5vcm1hbGl6YXRpb24KCkNMUiBub3JtYWxpemF0aW9uIGlzIHRoZSBzdGFuZGFyZCBtZXRob2QgaW4gU2V1cmF0IGZvciBBRFQgZGF0YSwgdHJlYXRpbmcgdGhlIHByb3RlaW4gY291bnRzIGFzIGNvbXBvc2l0aW9uYWwgZGF0YS4KCiAjIG1hcmdpbj0xIG1lYW5zICJwZXJmb3JtIENMUiBub3JtYWxpemF0aW9uIGZvciBlYWNoIGZlYXR1cmUgaW5kZXBlbmRlbnRseSIKICAgICMgbWFyZ2luPTIgbWVhbnMgInBlcmZvcm0gQ0xSIG5vcm1hbGl6YXRpb24gd2l0aGluIGEgY2VsbCIKCjxpbWcgc3JjPSJhZHRfbWFyZ2luLnBuZyIgYWx0PSJBbHQgdGV4dCIgd2lkdGg9IjUwMCIgaGVpZ2h0PSIxMDAiPgoKCmBgYHtyIGFkdF9ub3JtYWxpemF0aW9uLCBldmFsPUZBTFNFfQojIEFwcGx5IENMUiBub3JtYWxpemF0aW9uIChtYXJnaW4gPSAyIG5vcm1hbGl6ZXMgYWNyb3NzIGZlYXR1cmVzIGZvciBlYWNoIGNlbGwpCkFsbF9zYW1wbGVzX01lcmdlZCA8LSBOb3JtYWxpemVEYXRhKEFsbF9zYW1wbGVzX01lcmdlZCwgYXNzYXkgPSAiQURUIiwgbm9ybWFsaXphdGlvbi5tZXRob2QgPSAiQ0xSIiwgbWFyZ2luID0gMSkKYGBgCgojIyBRdWFsaXR5IGNvbnRyb2wgYWZ0ZXIgbm9tcmxpemF0aW9uCmBgYHtyIGFkdF9xYzMsIGZpZy5oZWlnaHQ9IDI0LCBmaWcud2lkdGg9IDMwfQojIElkZW50aWZ5IHBvdGVudGlhbCBvdXRsaWVyIGFudGlib2RpZXMgKGUuZy4sIGhpZ2hseSBleHByZXNzZWQgYWNyb3NzIGFsbCBjZWxscykKIyBQbG90IHJhdyBjb3VudHMgZm9yIGFsbCAyOCBwcm90ZWlucwphZHRfZmVhdHVyZXMgPC0gcm93bmFtZXMoQWxsX3NhbXBsZXNfTWVyZ2VkW1siQURUIl1dKQpwMyA8LSBWbG5QbG90KEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZXMgPSBhZHRfZmVhdHVyZXNbMTptaW4oMjgsIGxlbmd0aChhZHRfZmVhdHVyZXMpKV0sIG5jb2wgPSA0LCBwdC5zaXplID0gMCkKcHJpbnQocDMpCmBgYAoKIyMgVmlzdWFsaXplIG11bHRpcGxlIG1vZGFsaXRpZXMgc2lkZS1ieS1zaWRlCmBgYHtyICwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9MTB9CklkZW50cyhBbGxfc2FtcGxlc19NZXJnZWQpIDwtICJjZWxsX2xpbmUiCiMgTm93LCB3ZSB3aWxsIHZpc3VhbGl6ZSBDRDE0IGxldmVscyBmb3IgUk5BIGFuZCBwcm90ZWluIEJ5IHNldHRpbmcgdGhlIGRlZmF1bHQgYXNzYXksIHdlIGNhbgojIHZpc3VhbGl6ZSBvbmUgb3IgdGhlIG90aGVyCkRlZmF1bHRBc3NheShBbGxfc2FtcGxlc19NZXJnZWQpIDwtICJTQ1QiCnAxIDwtIEZlYXR1cmVQbG90KEFsbF9zYW1wbGVzX01lcmdlZCwgIkNENCIsIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAiZGFya2dyZWVuIikpICsgZ2d0aXRsZSgiQ0Q0IHByb3RlaW4iKQpEZWZhdWx0QXNzYXkoQWxsX3NhbXBsZXNfTWVyZ2VkKSA8LSAiU0NUIgpwMiA8LSBGZWF0dXJlUGxvdChBbGxfc2FtcGxlc19NZXJnZWQsICJDRDQiKSArIGdndGl0bGUoIkNENCBSTkEiKQoKIyBwbGFjZSBwbG90cyBzaWRlLWJ5LXNpZGUKcDEgfCBwMgoKS2V5KEFsbF9zYW1wbGVzX01lcmdlZFtbIlJOQSJdXSkKCktleShBbGxfc2FtcGxlc19NZXJnZWRbWyJTQ1QiXV0pCgpLZXkoQWxsX3NhbXBsZXNfTWVyZ2VkW1siQURUIl1dKQoKCiMgTm93LCB3ZSBjYW4gaW5jbHVkZSB0aGUga2V5IGluIHRoZSBmZWF0dXJlIG5hbWUsIHdoaWNoIG92ZXJyaWRlcyB0aGUgZGVmYXVsdCBhc3NheQpwMSA8LSBGZWF0dXJlUGxvdChBbGxfc2FtcGxlc19NZXJnZWQsICJhZHRfUEQxIiwgY29scyA9IGMoImxpZ2h0Z3JleSIsICJkYXJrZ3JlZW4iKSkgKyBnZ3RpdGxlKCJQRDEgcHJvdGVpbiIpCnAyIDwtIEZlYXR1cmVQbG90KEFsbF9zYW1wbGVzX01lcmdlZCwgInJuYV9QRENEMSIpICsgZ2d0aXRsZSgiUEQxIFJOQSIpCnAzIDwtIEZlYXR1cmVQbG90KEFsbF9zYW1wbGVzX01lcmdlZCwgInNjdF9QRENEMSIpICsgZ2d0aXRsZSgiUEQxIFNDVCIpCnAxIHwgcDIgfCBwMwoKIyBOb3csIHdlIGNhbiBpbmNsdWRlIHRoZSBrZXkgaW4gdGhlIGZlYXR1cmUgbmFtZSwgd2hpY2ggb3ZlcnJpZGVzIHRoZSBkZWZhdWx0IGFzc2F5CnAxIDwtIEZlYXR1cmVQbG90KEFsbF9zYW1wbGVzX01lcmdlZCwgImFkdF9DRDI3NCIsIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAiZGFya2dyZWVuIikpICsgZ2d0aXRsZSgiQ0QyNzQgcHJvdGVpbiIpCnAyIDwtIEZlYXR1cmVQbG90KEFsbF9zYW1wbGVzX01lcmdlZCwgInJuYV9DRDI3NCIpICsgZ2d0aXRsZSgiQ0QyNzQgUk5BIikKcDMgPC0gRmVhdHVyZVBsb3QoQWxsX3NhbXBsZXNfTWVyZ2VkLCAic2N0X0NEMjc0IikgKyBnZ3RpdGxlKCJDRDI3NCBSTkFfU0NUIikKcDEgfCBwMiB8IHAzCgojIE5vdywgd2UgY2FuIGluY2x1ZGUgdGhlIGtleSBpbiB0aGUgZmVhdHVyZSBuYW1lLCB3aGljaCBvdmVycmlkZXMgdGhlIGRlZmF1bHQgYXNzYXkKcDEgPC0gRmVhdHVyZVBsb3QoQWxsX3NhbXBsZXNfTWVyZ2VkLCAiYWR0X0NENyIsIGNvbHMgPSBjKCJsaWdodGdyZXkiLCAiZGFya2dyZWVuIikpICsgZ2d0aXRsZSgiQ0Q3IHByb3RlaW4iKQpwMiA8LSBGZWF0dXJlUGxvdChBbGxfc2FtcGxlc19NZXJnZWQsICJybmFfQ0Q3IikgKyBnZ3RpdGxlKCJDRDcgUk5BIikKcDMgPC0gRmVhdHVyZVBsb3QoQWxsX3NhbXBsZXNfTWVyZ2VkLCAic2N0X0NENyIpICsgZ2d0aXRsZSgiQ0Q3IFJOQV9TQ1QiKQpwMSB8IHAyIHwgcDMKYGBgCgpgYGB7ciAsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEwfQpEZWZhdWx0QXNzYXkgLT4gIkFEVCIKSWRlbnRzKG9iamVjdD1BbGxfc2FtcGxlc19NZXJnZWQpIDwtICJvcmlnLmlkZW50IgpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0QyIikKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiYWR0X0NEMyIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DRDUiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0QyOCIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DRDEyNyIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DRDQ1IikKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiYWR0X0NEMTkiKQoKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiYWR0X0NENyIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DRDI1IikKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiYWR0X0NEMjYiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0Q0NVJBIikKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiYWR0X0NENDVSTyIpCgpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0Q2MkwiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0NSNyIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DQ1I0IikKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiYWR0X0NDUjYiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0NSOCIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DQ1IxMCIpCgpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ1hDUjMiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ1hDUjQiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0Q5NSIpCgpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0QzMCIpCkZlYXR1cmVTY2F0dGVyKEFsbF9zYW1wbGVzX01lcmdlZCwgZmVhdHVyZTEgPSAiYWR0X0NENCIsIGZlYXR1cmUyID0gImFkdF9DRDQwIikKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiYWR0X0NENDQiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0Q0NSIpCgpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfQ0QyNzQiKQpGZWF0dXJlU2NhdHRlcihBbGxfc2FtcGxlc19NZXJnZWQsIGZlYXR1cmUxID0gImFkdF9DRDQiLCBmZWF0dXJlMiA9ICJhZHRfUEQxIikKRmVhdHVyZVNjYXR0ZXIoQWxsX3NhbXBsZXNfTWVyZ2VkLCBmZWF0dXJlMSA9ICJhZHRfQ0Q0IiwgZmVhdHVyZTIgPSAiVENSYWIiKQpgYGAKCmBgYHtyIEFEVCwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTJ9CgpsaWJyYXJ5KFNldXJhdCkKbGlicmFyeShkaXR0b1NlcSkKbGlicmFyeShSQ29sb3JCcmV3ZXIpCgojIE1ha2Ugc3VyZSBkZWZhdWx0IGFzc2F5IGlzIEFEVApEZWZhdWx0QXNzYXkoQWxsX3NhbXBsZXNfTWVyZ2VkKSA8LSAiQURUIgoKIyBLZWVwIG9ubHkgY2VsbHMgdGhhdCBoYXZlIEFEVCBkYXRhCmFkdF9jZWxscyA8LSBjb2xuYW1lcyhBbGxfc2FtcGxlc19NZXJnZWRbWyJBRFQiXV1AZGF0YSkKQWxsX3NhbXBsZXNfQURUIDwtIHN1YnNldChBbGxfc2FtcGxlc19NZXJnZWQsIGNlbGxzID0gYWR0X2NlbGxzKQoKIyBDaG9vc2UgYSByZWFzb25hYmxlIHNldCBvZiBwcm90ZWlucyB0byBwbG90CmdlbmVzX3RvX3Bsb3QgPC0gcm93bmFtZXMoQWxsX3NhbXBsZXNfQURUW1siQURUIl1dKQojIE9wdGlvbmFsOiByZW1vdmUgcHJvdGVpbnMgd2l0aCAwIGV4cHJlc3Npb24gYWNyb3NzIGFsbCBjZWxscwpnZW5lc190b19wbG90IDwtIGdlbmVzX3RvX3Bsb3Rbcm93U3VtcyhBbGxfc2FtcGxlc19BRFRbWyJBRFQiXV1AZGF0YSkgPiAwXQoKIyBDdXN0b20gY29sb3IgcGFsZXR0ZQpteV9jb2xvcnMgPC0gY29sb3JSYW1wUGFsZXR0ZShicmV3ZXIucGFsKDksICJZbEduQnUiKSkoMTAwKQoKIyBSdW4gZGl0dG9IZWF0bWFwCmRpdHRvSGVhdG1hcCgKICBBbGxfc2FtcGxlc19BRFQsCiAgZ2VuZXMgPSBnZW5lc190b19wbG90LAogIGFubm90LmJ5ID0gIlBhdGllbnRfb3JpZ2luIiwgICAgICAjIG9yICJvcmlnLmlkZW50IgogIGhlYXRtYXAuY29sb3JzID0gbXlfY29sb3JzLAogIHNjYWxlZC50by5tYXggPSBUUlVFCikKCgpgYGAKCgpgYGB7ciBBRFQyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMn0KCgpyb3duYW1lcyhBbGxfc2FtcGxlc19NZXJnZWRbWyJBRFQiXV0pCgpEZWZhdWx0QXNzYXkoQWxsX3NhbXBsZXNfTWVyZ2VkKSA8LSAiQURUIgoKCgpsaWJyYXJ5KGRpdHRvU2VxKQoKZGl0dG9IZWF0bWFwKEFsbF9zYW1wbGVzX01lcmdlZCwgZ2VuZXMsCiAgICBhbm5vdC5ieSA9IGMoIm9yaWcuaWRlbnQiKSkKCiMgTG9hZCBhZGRpdGlvbmFsIGxpYnJhcmllcyBmb3IgY29sb3IgcGFsZXR0ZXMKbGlicmFyeShSQ29sb3JCcmV3ZXIpCgojIERlZmluZSBhIGN1c3RvbSBjb2xvciBwYWxldHRlIGZvciB0aGUgaGVhdG1hcApteV9jb2xvcnMgPC0gY29sb3JSYW1wUGFsZXR0ZShicmV3ZXIucGFsKDksICJZbEduQnUiKSkoMTAwKSAgIyBFeGFtcGxlOiBZZWxsb3cgdG8gQmx1ZQoKIyBHZW5lcmF0ZSB0aGUgaGVhdG1hcCB3aXRoIGN1c3RvbSBjb2xvcnMKZGl0dG9IZWF0bWFwKAogIEFsbF9zYW1wbGVzX01lcmdlZCwgCiAgZ2VuZXMsCiAgYW5ub3QuYnkgPSBjKCJvcmlnLmlkZW50IiksCiAgaGVhdG1hcC5jb2xvcnMgPSBteV9jb2xvcnMsICAjIEFwcGx5IGN1c3RvbSBoZWF0bWFwIGNvbG9ycwogIHNjYWxlZC50by5tYXggPSBUUlVFICAgICAgICAgIyBPcHRpb25hbGx5LCBzY2FsZSBkYXRhIGZvciBiZXR0ZXIgY29udHJhc3QKKQoKYGBgCgoKCgpgYGB7ciBkYXRhMywgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTB9CgpEZWZhdWx0QXNzYXkoQWxsX3NhbXBsZXNfTWVyZ2VkKSA8LSAnQURUJwoKIyBHZXQgY2x1c3RlciBpbmZvcm1hdGlvbgpjbHVzdGVyX2luZm8gPC0gQWxsX3NhbXBsZXNfTWVyZ2VkJHNldXJhdF9jbHVzdGVycwpwcmludCh0YWJsZShjbHVzdGVyX2luZm8pKSAgIyBUaGlzIHdpbGwgc2hvdyB0aGUgZGlzdHJpYnV0aW9uIG9mIGNlbGxzIGFjcm9zcyBjbHVzdGVycwoKIyBHZXQgdW5pcXVlIGNsdXN0ZXIgSURzCmNsdXN0ZXJzIDwtIHNvcnQodW5pcXVlKGNsdXN0ZXJfaW5mbykpCnByaW50KGNsdXN0ZXJzKSAgIyBUaGlzIHdpbGwgc2hvdyB5b3Ugd2hhdCBjbHVzdGVyIElEcyBhcmUgYWN0dWFsbHkgcHJlc2VudAoKIyBDYWxjdWxhdGUgbWVhbiBleHByZXNzaW9uIGZvciBlYWNoIHByb3RlaW4gaW4gZWFjaCBjbHVzdGVyCm1lYW5fZXhwcmVzc2lvbl9jbHVzdGVycyA8LSBzYXBwbHkoY2x1c3RlcnMsIGZ1bmN0aW9uKGNsKSB7CiAgY2VsbHMgPC0gbmFtZXMoY2x1c3Rlcl9pbmZvKVtjbHVzdGVyX2luZm8gPT0gY2xdCiAgcm93TWVhbnMoTGF5ZXJEYXRhKEFsbF9zYW1wbGVzX01lcmdlZCwgYXNzYXkgPSAiQURUIiwgbGF5ZXIgPSAiZGF0YSIpWywgY2VsbHMsIGRyb3AgPSBGQUxTRV0pCn0pCgojIENoZWNrIHRoZSBkaW1lbnNpb25zIG9mIHRoZSByZXN1bHRpbmcgbWF0cml4CnByaW50KGRpbShtZWFuX2V4cHJlc3Npb25fY2x1c3RlcnMpKQoKIyBDcmVhdGUgY2x1c3RlciBsYWJlbHMgc3RhcnRpbmcgZnJvbSAwCmNsdXN0ZXJfbGFiZWxzIDwtIHBhc3RlMCgiQ2x1c3RlciAiLCBzZXEoMCwgbGVuZ3RoKGNsdXN0ZXJzKSAtIDEpKQoKIyBTZXQgY29sdW1uIG5hbWVzCmNvbG5hbWVzKG1lYW5fZXhwcmVzc2lvbl9jbHVzdGVycykgPC0gY2x1c3Rlcl9sYWJlbHMKCiMgQ3JlYXRlIHRoZSBoZWF0bWFwCnBoZWF0bWFwKG1lYW5fZXhwcmVzc2lvbl9jbHVzdGVycywgCiAgICAgICAgIG1haW4gPSAiUHJvdGVpbiBFeHByZXNzaW9uIEhlYXRtYXAgYnkgQ2x1c3RlciIsCiAgICAgICAgIHNjYWxlID0gInJvdyIsCiAgICAgICAgIGNsdXN0ZXJfcm93cyA9IFRSVUUsIAogICAgICAgICBjbHVzdGVyX2NvbHMgPSBUUlVFLAogICAgICAgICBzaG93X3Jvd25hbWVzID0gVFJVRSwKICAgICAgICAgc2hvd19jb2xuYW1lcyA9IFRSVUUsCiAgICAgICAgIGZvbnRzaXplX2NvbCA9IDgsCiAgICAgICAgIGFuZ2xlX2NvbCA9IDQ1KQoKIyBGb3IgdGhlIHRyZWUgdmlzdWFsaXphdGlvbgpkaXN0X21hdHJpeF9jbHVzdGVycyA8LSBkaXN0KHQobWVhbl9leHByZXNzaW9uX2NsdXN0ZXJzKSkKdHJlZV9jbHVzdGVycyA8LSBuaihkaXN0X21hdHJpeF9jbHVzdGVycykKCnBsb3QodHJlZV9jbHVzdGVycywgbWFpbiA9ICJDbHVzdGVyIFNpbWlsYXJpdHkgVHJlZSBCYXNlZCBvbiBQcm90ZWluIEV4cHJlc3Npb24iKQoKYGBgCg==