title: “Running DESeq2 pipeline” output: html_notebook
# Running DESeq2
This code performs differential expression analysis to identify deferentially expressed genes (DEGs).
First, it reads in a count matrix of isoform counts generated by `kallisto` and `trinity`, with row names set to the gene/transcript IDs and the first column removed. It then rounds the counts to whole numbers.
The `results()` function is used to extract the results table, which is ordered by gene/transcript ID.
The code then prints the top few rows of the results table and calculates the number of DEGs with an adjusted p-value less than or equal to 0.05. It plots the log2 fold changes versus the mean normalized counts for all genes, highlighting significant DEGs in red and adding horizontal lines at 2-fold upregulation and downregulation. Finally, it writes the list of significant DEGs to a file called "DEGlist.tab".
### Install Packages
if ("DESeq2" %in% rownames(installed.packages()) == 'FALSE') BiocManager::install('DESeq2')
if ("kableExtra" %in% rownames(installed.packages()) == 'FALSE') install.packages('kableExtra')
Load Libraries
library(DESeq2)
library(kableExtra)
library(tidyverse)
library(ggplot2)
Read in count matrix
countmatrix <- read.delim("../output/trinity-matrix/mcap.isoform.counts.matrix", header = TRUE, sep = '\t')
rownames(countmatrix) <- countmatrix$X
countmatrix <- countmatrix[,-1]
head(countmatrix)
##Round integers up to whole numbers for analysis
countmatrix <- round(countmatrix, 0)
head(countmatrix)
#ggplot(countmatrix) + geom_histogram(aes(x=sex), stat="bin", bins= 200) + xlab("Raw expression counts") + ylab("Number of genes") + xlim(0, 25000) + ylim(0, 5000)
Create Dataframe
# make a dataframe of 4 embryos and 4 recruits
deseq2.colData <- data.frame(condition=factor(c(rep("embryos", 4), rep("recruits", 4))),
type=factor(rep("single-read", 8)))
# set row names to match the column names in the count matrix
rownames(deseq2.colData) <- colnames(data)
# DESeqDataSet object created using the `DESeqDataSetFromMatrix` function
deseq2.dds <- DESeqDataSetFromMatrix(countData = countmatrix,
colData = deseq2.colData,
design = ~ condition)
Negative Binomial: DESeq2 Results
deseq2.dds <- DESeq(deseq2.dds)
deseq2.res <- results(deseq2.dds)
deseq2.res <- deseq2.res[order(rownames(deseq2.res)), ]
dim(deseq2.res)
summary(deseq2.res)
# deseq2.res will be used in step 6.. is there a way to save this and read it in, rather than coming back and re-running this code?
deseq2.sig.res <- (deseq2.res[!is.na(deseq2.res$padj) & deseq2.res$padj <= 0.05, ])
head(deseq2.sig.res)
# calculate the dimensions of the resulting subset. The dim() function returns a vector with two elements: the number of rows and the number of columns in the subset.
dim(deseq2.res[!is.na(deseq2.res$padj) & deseq2.res$padj <= 0.05, ])
Plotting
PCA
vsd <- vst(deseq2.dds, blind = FALSE)
pca <- plotPCA(vsd, intgroup = "condition")
pca
ggsave("../figs/pca.png", plot = pca, width = 8, height = 4, dpi = 600)
Heatmap
Install Packages
if ("pheatmap" %in% rownames(installed.packages()) == 'FALSE') install.packages('pheatmap')
Load Libraries
library(pheatmap)
Top 50 Differentially Expressed Genes
# Select top 20 differentially expressed genes
res <- results(deseq2.dds)
res_ordered <- res[order(res$padj), ]
top_genes <- row.names(res_ordered)[1:20]
# Extract counts and normalize
counts <- counts(deseq2.dds, normalized = TRUE)
counts_top <- counts[top_genes, ]
# Log-transform counts
log_counts_top <- log2(counts_top + 1)
# Generate heatmap
heatmap_20 <- pheatmap(log_counts_top, scale = "row")
ggsave("../figs/heatmap_20.png", plot = heatmap_20, width = 8, height = 5, dpi = 600)
heatmap_20
Fancy Volcano
# The main plot
fancy_volcano <- plot(deseq2.res$baseMean, deseq2.res$log2FoldChange,
pch=20, cex=0.45, ylim=c(-3, 3), log="x", col="darkgray",
main="DEG Coral Early Life History (pval <= 0.05)",
xlab="mean of normalized counts",
ylab="Log2 Fold Change")
points(deseq2.sig.res$baseMean, deseq2.sig.res$log2FoldChange, pch=20, cex=0.45, col="red")
abline(h=c(-1,1), col="blue")
dev.print(png, file = "../figs/fancy-volcano.png", width = 6, height = 4, units = "in", res = 300)
Basic Volcano
# Prepare the data for plotting
res_df <- as.data.frame(deseq2.res)
res_df$gene <- row.names(res_df)
# Create volcano plot
basic_volcano <- ggplot(res_df, aes(x = log2FoldChange, y = -log10(padj), color = padj < 0.05)) +
geom_point(alpha = 0.6, size = 1.5) +
scale_color_manual(values = c("grey", "red")) +
labs(title = "Volcano Plot",
x = "Log2 Fold Change",
y = "-Log10 Adjusted P-value",
color = "Significantly\nDifferentially Expressed") +
theme_minimal() +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.position = "top")
# Save plot
ggsave(path = "../figs", filename="basic-volcano.png")
# Print plot
print(basic_volcano)
Save the list of Differentially Expressed Genes (DEGs)!
Write the output to a table
mkdir -p ../output/deseq
write.table(deseq2.res, "../output/deseq/DEGlist.tab", sep = '\t', row.names = T)
Let’s look at this list
deglist <- read.csv("../output/deseq/DEGlist.tab", sep = '\t', header = TRUE)
deglist$RowName <- rownames(deglist)
deglist2 <- deglist[, c("RowName", "pvalue")] # Optionally, reorder the columns
head(deglist2)
#rank-sum Wilcoxon test for differential expressed genes
#Run the rank-sum Wilcoxon test for each gene
pvalues <- sapply(1:nrow(count_norm),function(i){
data<-cbind.data.frame(gene=as.numeric(t(count_norm[i,])),conditions)
p=wilcox.test(gene~conditions, data)$p.value
return(p)
})
fdr=p.adjust(pvalues,method = "fdr")
#Calculate fold-change for each gene
conditionsLevel<-levels(conditions)
dataCon1=count_norm[,c(which(conditions==conditionsLevel[1]))]
dataCon2=count_norm[,c(which(conditions==conditionsLevel[2]))]
foldChanges=log2(rowMeans(dataCon2)/rowMeans(dataCon1))
###Output results base on FDR threshold
outRst<-data.frame(log2foldChange=foldChanges, pValues=pvalues, FDR=fdr)
rownames(outRst)=rownames(count_norm)
LS0tCm91dHB1dDoKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdAotLS0KLS0tCnRpdGxlOiAiUnVubmluZyBERVNlcTIgcGlwZWxpbmUiCm91dHB1dDogaHRtbF9ub3RlYm9vawoKYGBge3J9CiMgUnVubmluZyBERVNlcTIKClRoaXMgY29kZSBwZXJmb3JtcyBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyB0byBpZGVudGlmeSBkZWZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyAoREVHcykuCgpGaXJzdCwgaXQgcmVhZHMgaW4gYSBjb3VudCBtYXRyaXggb2YgaXNvZm9ybSBjb3VudHMgZ2VuZXJhdGVkIGJ5IGBrYWxsaXN0b2AgYW5kIGB0cmluaXR5YCwgd2l0aCByb3cgbmFtZXMgc2V0IHRvIHRoZSBnZW5lL3RyYW5zY3JpcHQgSURzIGFuZCB0aGUgZmlyc3QgY29sdW1uIHJlbW92ZWQuIEl0IHRoZW4gcm91bmRzIHRoZSBjb3VudHMgdG8gd2hvbGUgbnVtYmVycy4KClRoZSBgcmVzdWx0cygpYCBmdW5jdGlvbiBpcyB1c2VkIHRvIGV4dHJhY3QgdGhlIHJlc3VsdHMgdGFibGUsIHdoaWNoIGlzIG9yZGVyZWQgYnkgZ2VuZS90cmFuc2NyaXB0IElELgoKVGhlIGNvZGUgdGhlbiBwcmludHMgdGhlIHRvcCBmZXcgcm93cyBvZiB0aGUgcmVzdWx0cyB0YWJsZSBhbmQgY2FsY3VsYXRlcyB0aGUgbnVtYmVyIG9mIERFR3Mgd2l0aCBhbiBhZGp1c3RlZCBwLXZhbHVlIGxlc3MgdGhhbiBvciBlcXVhbCB0byAwLjA1LiBJdCBwbG90cyB0aGUgbG9nMiBmb2xkIGNoYW5nZXMgdmVyc3VzIHRoZSBtZWFuIG5vcm1hbGl6ZWQgY291bnRzIGZvciBhbGwgZ2VuZXMsIGhpZ2hsaWdodGluZyBzaWduaWZpY2FudCBERUdzIGluIHJlZCBhbmQgYWRkaW5nIGhvcml6b250YWwgbGluZXMgYXQgMi1mb2xkIHVwcmVndWxhdGlvbiBhbmQgZG93bnJlZ3VsYXRpb24uIEZpbmFsbHksIGl0IHdyaXRlcyB0aGUgbGlzdCBvZiBzaWduaWZpY2FudCBERUdzIHRvIGEgZmlsZSBjYWxsZWQgIkRFR2xpc3QudGFiIi4KCiMjIyBJbnN0YWxsIFBhY2thZ2VzCgpgYGB7ciBpbnN0YWxsLXBhY2thZ2VzLCBmaWxlbmFtZT0ncid9CmlmICgiREVTZXEyIiAlaW4lIHJvd25hbWVzKGluc3RhbGxlZC5wYWNrYWdlcygpKSA9PSAnRkFMU0UnKSBCaW9jTWFuYWdlcjo6aW5zdGFsbCgnREVTZXEyJykKaWYgKCJrYWJsZUV4dHJhIiAlaW4lIHJvd25hbWVzKGluc3RhbGxlZC5wYWNrYWdlcygpKSA9PSAnRkFMU0UnKSBpbnN0YWxsLnBhY2thZ2VzKCdrYWJsZUV4dHJhJykKYGBgCgojIyMgTG9hZCBMaWJyYXJpZXMKCmBgYHtyLCBmaWxlbmFtZSA9ICdyJ30KbGlicmFyeShERVNlcTIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ2dwbG90MikKYGBgCgojIyMgUmVhZCBpbiBjb3VudCBtYXRyaXgKCmBgYHtyLCBmaWxlbmFtZSA9ICdyJ30KY291bnRtYXRyaXggPC0gcmVhZC5kZWxpbSgiLi4vb3V0cHV0L3RyaW5pdHktbWF0cml4L21jYXAuaXNvZm9ybS5jb3VudHMubWF0cml4IiwgaGVhZGVyID0gVFJVRSwgc2VwID0gJ1x0JykKcm93bmFtZXMoY291bnRtYXRyaXgpIDwtIGNvdW50bWF0cml4JFgKY291bnRtYXRyaXggPC0gY291bnRtYXRyaXhbLC0xXQpoZWFkKGNvdW50bWF0cml4KQpgYGAKCiMjUm91bmQgaW50ZWdlcnMgdXAgdG8gd2hvbGUgbnVtYmVycyBmb3IgYW5hbHlzaXMKCmBgYHtyLCBmaWxlbmFtZSA9ICdyJ30KY291bnRtYXRyaXggPC0gcm91bmQoY291bnRtYXRyaXgsIDApCmhlYWQoY291bnRtYXRyaXgpCiNnZ3Bsb3QoY291bnRtYXRyaXgpICsgZ2VvbV9oaXN0b2dyYW0oYWVzKHg9c2V4KSwgc3RhdD0iYmluIiwgYmlucz0gMjAwKSArIHhsYWIoIlJhdyBleHByZXNzaW9uIGNvdW50cyIpICsgeWxhYigiTnVtYmVyIG9mIGdlbmVzIikgKyB4bGltKDAsIDI1MDAwKSArIHlsaW0oMCwgNTAwMCkKYGBgCgojIyMgQ3JlYXRlIERhdGFmcmFtZQoKCgpgYGB7ciwgZmlsZW5hbWUgPSAncid9CiMgbWFrZSBhIGRhdGFmcmFtZSBvZiA0IGVtYnJ5b3MgYW5kIDQgcmVjcnVpdHMKZGVzZXEyLmNvbERhdGEgPC0gZGF0YS5mcmFtZShjb25kaXRpb249ZmFjdG9yKGMocmVwKCJlbWJyeW9zIiwgNCksIHJlcCgicmVjcnVpdHMiLCA0KSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlPWZhY3RvcihyZXAoInNpbmdsZS1yZWFkIiwgOCkpKQoKIyBzZXQgcm93IG5hbWVzIHRvIG1hdGNoIHRoZSBjb2x1bW4gbmFtZXMgaW4gdGhlIGNvdW50IG1hdHJpeApyb3duYW1lcyhkZXNlcTIuY29sRGF0YSkgPC0gY29sbmFtZXMoZGF0YSkKCiMgREVTZXFEYXRhU2V0IG9iamVjdCBjcmVhdGVkIHVzaW5nIHRoZSBgREVTZXFEYXRhU2V0RnJvbU1hdHJpeGAgZnVuY3Rpb24KZGVzZXEyLmRkcyA8LSBERVNlcURhdGFTZXRGcm9tTWF0cml4KGNvdW50RGF0YSA9IGNvdW50bWF0cml4LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sRGF0YSA9IGRlc2VxMi5jb2xEYXRhLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlc2lnbiA9IH4gY29uZGl0aW9uKQpgYGAKCiMjIyBOZWdhdGl2ZSBCaW5vbWlhbDogREVTZXEyIFJlc3VsdHMKCgoKYGBge3IsIGZpbGVuYW1lPSdyJ30KZGVzZXEyLmRkcyA8LSBERVNlcShkZXNlcTIuZGRzKQpkZXNlcTIucmVzIDwtIHJlc3VsdHMoZGVzZXEyLmRkcykKZGVzZXEyLnJlcyA8LSBkZXNlcTIucmVzW29yZGVyKHJvd25hbWVzKGRlc2VxMi5yZXMpKSwgXQpkaW0oZGVzZXEyLnJlcykKc3VtbWFyeShkZXNlcTIucmVzKQojIGRlc2VxMi5yZXMgd2lsbCBiZSB1c2VkIGluIHN0ZXAgNi4uIGlzIHRoZXJlIGEgd2F5IHRvIHNhdmUgdGhpcyBhbmQgcmVhZCBpdCBpbiwgcmF0aGVyIHRoYW4gY29taW5nIGJhY2sgYW5kIHJlLXJ1bm5pbmcgdGhpcyBjb2RlPyAKCmBgYAoKCmBgYHtyfQpkZXNlcTIuc2lnLnJlcyA8LSAoZGVzZXEyLnJlc1shaXMubmEoZGVzZXEyLnJlcyRwYWRqKSAmIGRlc2VxMi5yZXMkcGFkaiA8PSAwLjA1LCBdKQoKaGVhZChkZXNlcTIuc2lnLnJlcykKCiMgY2FsY3VsYXRlIHRoZSBkaW1lbnNpb25zIG9mIHRoZSByZXN1bHRpbmcgc3Vic2V0LiBUaGUgZGltKCkgZnVuY3Rpb24gcmV0dXJucyBhIHZlY3RvciB3aXRoIHR3byBlbGVtZW50czogdGhlIG51bWJlciBvZiByb3dzIGFuZCB0aGUgbnVtYmVyIG9mIGNvbHVtbnMgaW4gdGhlIHN1YnNldC4KZGltKGRlc2VxMi5yZXNbIWlzLm5hKGRlc2VxMi5yZXMkcGFkaikgJiBkZXNlcTIucmVzJHBhZGogPD0gMC4wNSwgXSkKYGBgCgojIFBsb3R0aW5nCgojIyBQQ0EKCmBgYHtyfQp2c2QgPC0gdnN0KGRlc2VxMi5kZHMsIGJsaW5kID0gRkFMU0UpCnBjYSA8LSBwbG90UENBKHZzZCwgaW50Z3JvdXAgPSAiY29uZGl0aW9uIikKcGNhCgpnZ3NhdmUoIi4uL2ZpZ3MvcGNhLnBuZyIsIHBsb3QgPSBwY2EsIHdpZHRoID0gOCwgaGVpZ2h0ID0gNCwgZHBpID0gNjAwKQpgYGAKCiMjIEhlYXRtYXAKCiMjIyBJbnN0YWxsIFBhY2thZ2VzCgpgYGB7cn0KaWYgKCJwaGVhdG1hcCIgJWluJSByb3duYW1lcyhpbnN0YWxsZWQucGFja2FnZXMoKSkgPT0gJ0ZBTFNFJykgaW5zdGFsbC5wYWNrYWdlcygncGhlYXRtYXAnKSAKYGBgCgojIyMgTG9hZCBMaWJyYXJpZXMKCmBgYHtyfQpsaWJyYXJ5KHBoZWF0bWFwKQpgYGAKCiMjIyBUb3AgNTAgRGlmZmVyZW50aWFsbHkgRXhwcmVzc2VkIEdlbmVzCgpgYGB7ciwgZmlnLmhlaWdodD04LiwgZmlnLndpZHRoPTh9CgojIFNlbGVjdCB0b3AgMjAgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzCnJlcyA8LSByZXN1bHRzKGRlc2VxMi5kZHMpCnJlc19vcmRlcmVkIDwtIHJlc1tvcmRlcihyZXMkcGFkaiksIF0KdG9wX2dlbmVzIDwtIHJvdy5uYW1lcyhyZXNfb3JkZXJlZClbMToyMF0KCiMgRXh0cmFjdCBjb3VudHMgYW5kIG5vcm1hbGl6ZQpjb3VudHMgPC0gY291bnRzKGRlc2VxMi5kZHMsIG5vcm1hbGl6ZWQgPSBUUlVFKQpjb3VudHNfdG9wIDwtIGNvdW50c1t0b3BfZ2VuZXMsIF0KCiMgTG9nLXRyYW5zZm9ybSBjb3VudHMKbG9nX2NvdW50c190b3AgPC0gbG9nMihjb3VudHNfdG9wICsgMSkKCiMgR2VuZXJhdGUgaGVhdG1hcApoZWF0bWFwXzIwIDwtIHBoZWF0bWFwKGxvZ19jb3VudHNfdG9wLCBzY2FsZSA9ICJyb3ciKQoKZ2dzYXZlKCIuLi9maWdzL2hlYXRtYXBfMjAucG5nIiwgcGxvdCA9IGhlYXRtYXBfMjAsIHdpZHRoID0gOCwgaGVpZ2h0ID0gNSwgZHBpID0gNjAwKQoKaGVhdG1hcF8yMAoKYGBgCgojIyBGYW5jeSBWb2xjYW5vCgoKYGBge3IsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTh9CgojIFRoZSBtYWluIHBsb3QKZmFuY3lfdm9sY2FubyA8LSBwbG90KGRlc2VxMi5yZXMkYmFzZU1lYW4sIGRlc2VxMi5yZXMkbG9nMkZvbGRDaGFuZ2UsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHBjaD0yMCwgY2V4PTAuNDUsIHlsaW09YygtMywgMyksIGxvZz0ieCIsIGNvbD0iZGFya2dyYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgIG1haW49IkRFRyBDb3JhbCBFYXJseSBMaWZlIEhpc3RvcnkgIChwdmFsIDw9IDAuMDUpIiwKICAgICAgICAgICAgICAgICAgICAgICAgICB4bGFiPSJtZWFuIG9mIG5vcm1hbGl6ZWQgY291bnRzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICB5bGFiPSJMb2cyIEZvbGQgQ2hhbmdlIikgCiAgICAgICAgICAgICAgICAgIHBvaW50cyhkZXNlcTIuc2lnLnJlcyRiYXNlTWVhbiwgZGVzZXEyLnNpZy5yZXMkbG9nMkZvbGRDaGFuZ2UsIHBjaD0yMCwgY2V4PTAuNDUsIGNvbD0icmVkIikgCiAgICAgICAgICAgICAgICAgIGFibGluZShoPWMoLTEsMSksIGNvbD0iYmx1ZSIpCiAgICAgICAgICAgICAgICAgIApkZXYucHJpbnQocG5nLCBmaWxlID0gIi4uL2ZpZ3MvZmFuY3ktdm9sY2Fuby5wbmciLCB3aWR0aCA9IDYsIGhlaWdodCA9IDQsIHVuaXRzID0gImluIiwgcmVzID0gMzAwKQpgYGAKCgojIyBCYXNpYyBWb2xjYW5vCgpgYGB7ciwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9OH0KIyBQcmVwYXJlIHRoZSBkYXRhIGZvciBwbG90dGluZwpyZXNfZGYgPC0gYXMuZGF0YS5mcmFtZShkZXNlcTIucmVzKQpyZXNfZGYkZ2VuZSA8LSByb3cubmFtZXMocmVzX2RmKQoKIyBDcmVhdGUgdm9sY2FubyBwbG90CmJhc2ljX3ZvbGNhbm8gPC0gZ2dwbG90KHJlc19kZiwgYWVzKHggPSBsb2cyRm9sZENoYW5nZSwgeSA9IC1sb2cxMChwYWRqKSwgY29sb3IgPSBwYWRqIDwgMC4wNSkpICsKICAgICAgICAgICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC42LCBzaXplID0gMS41KSArCiAgICAgICAgICAgICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImdyZXkiLCAicmVkIikpICsKICAgICAgICAgICAgICAgICBsYWJzKHRpdGxlID0gIlZvbGNhbm8gUGxvdCIsCiAgICAgICAgICAgICAgICAgICAgICAgIHggPSAiTG9nMiBGb2xkIENoYW5nZSIsCiAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAiLUxvZzEwIEFkanVzdGVkIFAtdmFsdWUiLAogICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJTaWduaWZpY2FudGx5XG5EaWZmZXJlbnRpYWxseSBFeHByZXNzZWQiKSArCiAgICAgICAgICAgICAgICAgdGhlbWVfbWluaW1hbCgpICsKICAgICAgICAgICAgICAgICB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCgojIFNhdmUgcGxvdApnZ3NhdmUocGF0aCA9ICIuLi9maWdzIiwgZmlsZW5hbWU9ImJhc2ljLXZvbGNhbm8ucG5nIikKCiMgUHJpbnQgcGxvdApwcmludChiYXNpY192b2xjYW5vKQpgYGAKCiMgU2F2ZSB0aGUgbGlzdCBvZiBEaWZmZXJlbnRpYWxseSBFeHByZXNzZWQgR2VuZXMgKERFR3MpIQoKV3JpdGUgdGhlIG91dHB1dCB0byBhIHRhYmxlCgpgYGB7ciwgZW5naW5lPSdiYXNoJ30KbWtkaXIgLXAgLi4vb3V0cHV0L2Rlc2VxCmBgYAoKYGBge3J9CndyaXRlLnRhYmxlKGRlc2VxMi5yZXMsICIuLi9vdXRwdXQvZGVzZXEvREVHbGlzdC50YWIiLCBzZXAgPSAnXHQnLCByb3cubmFtZXMgPSBUKQpgYGAKCkxldCdzIGxvb2sgYXQgdGhpcyBsaXN0CgpgYGB7cn0KZGVnbGlzdCA8LSByZWFkLmNzdigiLi4vb3V0cHV0L2Rlc2VxL0RFR2xpc3QudGFiIiwgc2VwID0gJ1x0JywgaGVhZGVyID0gVFJVRSkKZGVnbGlzdCRSb3dOYW1lIDwtIHJvd25hbWVzKGRlZ2xpc3QpCmRlZ2xpc3QyIDwtIGRlZ2xpc3RbLCBjKCJSb3dOYW1lIiwgInB2YWx1ZSIpXSAjIE9wdGlvbmFsbHksIHJlb3JkZXIgdGhlIGNvbHVtbnMKaGVhZChkZWdsaXN0MikKYGBgCgpgYGB7cn0KI3Jhbmstc3VtIFdpbGNveG9uIHRlc3QgZm9yIGRpZmZlcmVudGlhbCBleHByZXNzZWQgZ2VuZXMKI1J1biB0aGUgcmFuay1zdW0gV2lsY294b24gdGVzdCBmb3IgZWFjaCBnZW5lCnB2YWx1ZXMgPC0gc2FwcGx5KDE6bnJvdyhjb3VudF9ub3JtKSxmdW5jdGlvbihpKXsKICAgICBkYXRhPC1jYmluZC5kYXRhLmZyYW1lKGdlbmU9YXMubnVtZXJpYyh0KGNvdW50X25vcm1baSxdKSksY29uZGl0aW9ucykKICAgICBwPXdpbGNveC50ZXN0KGdlbmV+Y29uZGl0aW9ucywgZGF0YSkkcC52YWx1ZQogICAgIHJldHVybihwKQogICB9KQpmZHI9cC5hZGp1c3QocHZhbHVlcyxtZXRob2QgPSAiZmRyIikKI0NhbGN1bGF0ZSBmb2xkLWNoYW5nZSBmb3IgZWFjaCBnZW5lCmNvbmRpdGlvbnNMZXZlbDwtbGV2ZWxzKGNvbmRpdGlvbnMpCmRhdGFDb24xPWNvdW50X25vcm1bLGMod2hpY2goY29uZGl0aW9ucz09Y29uZGl0aW9uc0xldmVsWzFdKSldCmRhdGFDb24yPWNvdW50X25vcm1bLGMod2hpY2goY29uZGl0aW9ucz09Y29uZGl0aW9uc0xldmVsWzJdKSldCmZvbGRDaGFuZ2VzPWxvZzIocm93TWVhbnMoZGF0YUNvbjIpL3Jvd01lYW5zKGRhdGFDb24xKSkKIyMjT3V0cHV0IHJlc3VsdHMgYmFzZSBvbiBGRFIgdGhyZXNob2xkCm91dFJzdDwtZGF0YS5mcmFtZShsb2cyZm9sZENoYW5nZT1mb2xkQ2hhbmdlcywgcFZhbHVlcz1wdmFsdWVzLCBGRFI9ZmRyKQpyb3duYW1lcyhvdXRSc3QpPXJvd25hbWVzKGNvdW50X25vcm0pCmBgYAoKCgoK