1. Gathering data for processing
1.1. Names of humnan membrane proteins and their genes
Downlooad the protein lists from UniProt, using the following query: annotation:(type:transmem) AND organism:"Homo sapiens (Human) [9606]
. Alternatively use this link to open the search results page.
Ensure to click Columns
button to add Topological domain
into the columns to be displayed, because this column will be needed for analyzing immunohistochemistry data. Click Download
to save the results, choosing the format Tab-separated
. The approximate downloaded file size is 1.4 MB. Unzip the file and rename it to Membrane-UniProt3.tab
for import.
1.2. Data from HPA
Download immunohistochemistry (IHC) values for all normal tissues
from The Human Protein Atlas Consortium, or directly from this link.
Similarly, download the staining profiles for proteins in human tumor
(including colorectal cancer) tissue based on immunohistochemisty from link. Unzip both files.
1.3. Data from EVPedia
Check the EVpedia site and download all proteins in the CVS format. Unzip and rename the file as EVpedia.cvs
. Or use the pre-downloaded file.
1.4 Data load into R
rm(list=ls(all=TRUE))
cancerName = '^colorectal'
)
uniprot = read.delim('Membrane-UniProt3.tab', sep='\t', header = TRUE, stringsAsFactors = FALSE)
normalTissueIHC = read.delim('normal_tissue.tsv', sep='\t', header = TRUE, stringsAsFactors = FALSE)
tummorTissueIHC = read.delim('pathology.tsv', sep='\t', header = TRUE, stringsAsFactors = FALSE)
cancerTissueIHC = tummorTissueIHC[grep(cancerName, tummorTissueIHC$Cancer), ]
evpedia = read.delim('evpedia.csv', sep=',', header = TRUE, stringsAsFactors = FALSE)
genesInEvpedia = unique(evpedia$UniProt.name)
2. Data processing
2.2. Cancer data
Cancer data are processed to obtain the mean staining score. Numerical values are assigned to staining levels,[High Mediuem Low Not.detected] = [3 2 1 0];
this assignment can be modified.
scores = diag(c(3, 2, 1, 0))
cancerTissueIHC$meanScores = rowMeans(as.matrix(cancerTissueIHC[, c("High", "Medium", "Low", "Not.detected")]) %*% scores)
By inspecting the number of unique gene names, we found the length of unique gene names is shorter than the score list.
cancerTissueIHC[duplicated(cancerTissueIHC$Gene.name),]$Gene.name
[1] "SDHD" "ARL14EPL" "COG8" "C2orf61" "MATR3" "HIST1H3D" "TXNRD3NB" "PRSS50" "LYNX1" "SCO2" "BTBD8" "RABGEF1" "LYNX1"
In addition to 13 records with duplicated gene names (with identical
or different mean IHC scores), there are also 4292 NA values. These two
data quality issues can be resolved by aggreating the mean scores by
gene names while removing NA values (default option) to get the
sanitized mean IHC scores for each gene for colorectal cancers.
sanitizedcancerTissueIHC = aggregate(meanScores ~ Gene.name, cancerTissueIHC, mean)
genesSelectedFromCancer = intersect(genesForExtracelluarProteins, sanitizedcancerTissueIHC$Gene.name)
genesSelectedForAnalysis = intersect(genesSelectedFromCancer, genesInEvpedia)
2.3. Normal tissue data
We perfome the similar processing with the normal tissue data. As the dataset is large, we limit genes used in the tumor.
genesInNormalTissueIHC = unique(normalTissueIHC$Gene.name)
genesSelectedForAnalysis = intersect(genesSelectedForAnalysis, genesInNormalTissueIHC)
selectedNormalTissueIHCRecords = normalTissueIHC[normalTissueIHC$Gene.name %in% genesSelectedForAnalysis,]
Normal tissue data have multiple IHC scores for a given gene, as the
gene is present in different types of tissue. We rearrange normal tissue
data to count the occurence of the IHC score for each gene.
selectedNormalTissueIHC = table(selectedNormalTissueIHCRecords$Gene.name, selectedNormalTissueIHCRecords$Level)
selectedNormalTissueIHC.meanScores = rowMeans(as.matrix(selectedNormalTissueIHC[, c("High", "Medium", "Low", "Not detected")]) %*% scores)
3. Plotting data
3.1. Linear data scaling
This routine uses a regular nomralization [min max] = [0 1]
.
library(scales)
scaledTumorTissueIHCScores = rescale(sanitizedcancerTissueIHC$meanScores)
names(scaledTumorTissueIHCScores) = sanitizedcancerTissueIHC$Gene.name
scaledSelectedTumorIHCScores = scaledTumorTissueIHCScores[genesSelectedForAnalysis]
scaledNormalTissueIHCScores = rescale(selectedNormalTissueIHC.meanScores)
scaledNormalTissueIHCScores = scaledNormalTissueIHCScores[genesSelectedForAnalysis]
Plotting data:
library(ggplot2)
library(ggrepel)
library(scico)
d = data.frame(x = scaledSelectedTumorIHCScores, y = scaledNormalTissueIHCScores, z = scaledSelectedTumorIHCScores - scaledNormalTissueIHCScores)
colormap = scico(256, palette = "vik", direction=1)
ggplot(d, aes(x, y)) + geom_point(aes(color = z)) +
labs(x = "Staining score in CRC tissue (a.u.)", y = "Staining score in normal tissue (a.u.)") +
labs(colour = "Difference\n[tumor - normal]") +
scale_colour_gradientn(limits=c(-1, 1), colours = colormap) +
geom_text_repel(aes(label=ifelse(z > 1.5, genesSelectedForAnalysis,'')), size = 3) +
theme(axis.title = element_text(size = 15), axis.text = element_text(size = 14)) +
coord_fixed(1)

3.2 Z-score
This routine scales the data to normal distribution.
mu = mean(sanitizedcancerTissueIHC$meanScores)
sigma = sd(sanitizedcancerTissueIHC$meanScores)
scaledTumorTissueZScores = (sanitizedcancerTissueIHC$meanScores - mu)/sigma
names(scaledTumorTissueZScores) = sanitizedcancerTissueIHC$Gene.name
scaledSelectedTumorZScores = scaledTumorTissueZScores[genesSelectedForAnalysis]
mu = mean(selectedNormalTissueIHC.meanScores)
sigma = sd(selectedNormalTissueIHC.meanScores)
scaledNormalTissueZScores = (selectedNormalTissueIHC.meanScores - mu)/sigma
scaledNormalTissueZScores = scaledNormalTissueZScores[genesSelectedForAnalysis]
Plotting data:
dz = data.frame(x = scaledSelectedTumorZScores, y = scaledNormalTissueZScores, z = scaledSelectedTumorZScores - scaledNormalTissueZScores)
ggplot(dz, aes(x, y)) + geom_point(aes(color = z)) +
labs(x = "Z score in CRC tissue (a.u.)", y = "Z score in normal tissue (a.u.)") +
labs(colour = "Difference\n[tumor - normal]") +
xlim(-1.5, 2.5) + ylim(-1.5, 2.5) +
scale_colour_gradientn(limits=c(-3, 3), colours = colormap) +
geom_text_repel(aes(label=ifelse(z > 1.5,genesSelectedForAnalysis,'')), size = 3) +
theme(axis.title = element_text(size = 15), axis.text = element_text(size = 14))+
coord_fixed(1)

LS0tCnRpdGxlOiAiQ1JDIG1hcmtlciBzZWxlY3Rpb24iCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KIyMjIyAxLiBHYXRoZXJpbmcgZGF0YSBmb3IgcHJvY2Vzc2luZwojIyMjIyAxLjEuIE5hbWVzIG9mIGh1bW5hbiBtZW1icmFuZSBwcm90ZWlucyBhbmQgdGhlaXIgZ2VuZXMKCkRvd25sb29hZCB0aGUgcHJvdGVpbiBsaXN0cyBmcm9tIFVuaVByb3QsIHVzaW5nIHRoZSBmb2xsb3dpbmcgcXVlcnk6IApgYW5ub3RhdGlvbjoodHlwZTp0cmFuc21lbSkgQU5EIG9yZ2FuaXNtOiJIb21vIHNhcGllbnMgKEh1bWFuKSBbOTYwNl1gLiBBbHRlcm5hdGl2ZWx5IHVzZSB0aGlzIFtsaW5rXShodHRwczovL3d3dy51bmlwcm90Lm9yZy91bmlwcm90Lz9xdWVyeT1hbm5vdGF0aW9uJTNBJTI4dHlwZSUzQXRyYW5zbWVtJTI5K29yZ2FuaXNtJTNBJTIySG9tbytzYXBpZW5zKyUyOEh1bWFuJTI5KyU1Qjk2MDYlNUQlMjImc29ydD1zY29yZSkgdG8gb3BlbiB0aGUgc2VhcmNoIHJlc3VsdHMgcGFnZS4gCgpFbnN1cmUgdG8gY2xpY2sgYENvbHVtbnNgIGJ1dHRvbiB0byBhZGQgYFRvcG9sb2dpY2FsIGRvbWFpbmAgaW50byB0aGUgY29sdW1ucyB0byBiZSBkaXNwbGF5ZWQsIGJlY2F1c2UgdGhpcyBjb2x1bW4gd2lsbCBiZSBuZWVkZWQgZm9yIGFuYWx5emluZyBpbW11bm9oaXN0b2NoZW1pc3RyeSBkYXRhLiBDbGljayBgRG93bmxvYWRgIHRvIHNhdmUgdGhlIHJlc3VsdHMsIGNob29zaW5nIHRoZSBmb3JtYXQgYFRhYi1zZXBhcmF0ZWRgLiBUaGUgYXBwcm94aW1hdGUgZG93bmxvYWRlZCBmaWxlIHNpemUgaXMgMS40IE1CLiBVbnppcCB0aGUgZmlsZSBhbmQgcmVuYW1lIGl0IHRvIGBNZW1icmFuZS1VbmlQcm90My50YWJgIGZvciBpbXBvcnQuCgojIyMjIyAxLjIuIERhdGEgZnJvbSBIUEEKRG93bmxvYWQgaW1tdW5vaGlzdG9jaGVtaXN0cnkgKElIQykgdmFsdWVzIGZvciBhbGwgbm9ybWFsIHRpc3N1ZXMgZnJvbSBUaGUgSHVtYW4gUHJvdGVpbiBBdGxhcyBDb25zb3J0aXVtLCBvciBkaXJlY3RseSBmcm9tIHRoaXMgW2xpbmtdKGh0dHBzOi8vd3d3LnByb3RlaW5hdGxhcy5vcmcvZG93bmxvYWQvbm9ybWFsX3Rpc3N1ZS50c3YuemlwKS4gU2ltaWxhcmx5LCBkb3dubG9hZCB0aGUgc3RhaW5pbmcgcHJvZmlsZXMgZm9yIHByb3RlaW5zIGluIGh1bWFuIHR1bW9yIChpbmNsdWRpbmcgY29sb3JlY3RhbCBjYW5jZXIpIHRpc3N1ZSBiYXNlZCBvbiBpbW11bm9oaXN0b2NoZW1pc3R5IGZyb20gW2xpbmtdKGh0dHBzOi8vd3d3LnByb3RlaW5hdGxhcy5vcmcvZG93bmxvYWQvcGF0aG9sb2d5LnRzdi56aXApLiBVbnppcCBib3RoIGZpbGVzLiAKCiMjIyMjIDEuMy4gRGF0YSBmcm9tIEVWUGVkaWEKQ2hlY2sgdGhlIFtFVnBlZGlhIHNpdGVdKGh0dHA6Ly9zdHVkZW50NC5wb3N0ZWNoLmFjLmtyL2V2cGVkaWEyX3hlL3hlL2luZGV4LnBocD9taWQ9VG9wX3Byb3RlaW5faHVtYW4pIGFuZCBkb3dubG9hZCBhbGwgcHJvdGVpbnMgaW4gdGhlIENWUyBmb3JtYXQuIFVuemlwIGFuZCByZW5hbWUgdGhlIGZpbGUgYXMgYEVWcGVkaWEuY3ZzYC4gT3IgdXNlIHRoZSBwcmUtZG93bmxvYWRlZCBmaWxlLgoKIyMjIyMgMS40IERhdGEgbG9hZCBpbnRvIFIKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcGFnZWQucHJpbnQ9RkFMU0V9CnJtKGxpc3Q9bHMoYWxsPVRSVUUpKSAjIENsZWFuIGFsbCB2YXJpYWJsZXMKCmNhbmNlck5hbWUgPSAnXmNvbG9yZWN0YWwnICMgQ2FuY2VyIHR5cGUKI3NldHdkKCIvVm9sdW1lcy9Xb3JrL01hcmtlcndvcmsvIikgIyBzZXQgdGhlIHdvcmtpbmcgZGlyZWN0b3J5CnNldHdkKCIvVm9sdW1lcy9Xb3JrL1JlZGhhd2svRHJvcGJveCAoUGVyc29uYWwpL0N1cnJlbnQgV29yay9QYXBlcnMvMjAxOCBDUkMgZXhvc29tZS8wOCBGaW5hbCBzdWJtaXNzaW9uLzAxIFN1Ym1pc3Npb24gZmlsZS9NYXJrZXJzZWxlY3Rpb24iKQp1bmlwcm90ID0gcmVhZC5kZWxpbSgnTWVtYnJhbmUtVW5pUHJvdDMudGFiJywgc2VwPSdcdCcsIGhlYWRlciA9IFRSVUUsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKbm9ybWFsVGlzc3VlSUhDID0gcmVhZC5kZWxpbSgnbm9ybWFsX3Rpc3N1ZS50c3YnLCBzZXA9J1x0JywgaGVhZGVyID0gVFJVRSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQp0dW1tb3JUaXNzdWVJSEMgPSByZWFkLmRlbGltKCdwYXRob2xvZ3kudHN2Jywgc2VwPSdcdCcsIGhlYWRlciA9IFRSVUUsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKIyBDaG9vc2Ugb25seSB0aGUgdGFyZ2V0IGNhbmNlciB0eXBlCmNhbmNlclRpc3N1ZUlIQyA9IHR1bW1vclRpc3N1ZUlIQ1tncmVwKGNhbmNlck5hbWUsIHR1bW1vclRpc3N1ZUlIQyRDYW5jZXIpLCBdIApldnBlZGlhID0gcmVhZC5kZWxpbSgnZXZwZWRpYS5jc3YnLCBzZXA9JywnLCBoZWFkZXIgPSBUUlVFLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmdlbmVzSW5FdnBlZGlhID0gdW5pcXVlKGV2cGVkaWEkVW5pUHJvdC5uYW1lKQpgYGAKCiMjIyMgMi4gRGF0YSBwcm9jZXNzaW5nIAojIyMjIyAyLjEuIEV4dHJhY3QgZ2VuZXMgZm9yIHByb2Nlc3NpbmcgCldlIGV4dHJhY3QgdW5pcXVlIGdlbmUgbmFtZXMgYXNzb2NpYXRlZCB3aXRoIG1lbWJyYW5lIHByb3RlaW5zIGZyb20gVW5pUHJvdC4gVGhlIHRvdGFsIG51bWJlciBvZiBnZW5lcyBpcyBjaGVja2VkLgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQptZW1icmFuZUdlbmVzID0gdW5pcXVlKHVubGlzdChzdHJzcGxpdCh1bmlwcm90JEdlbmUubmFtZXMsIHNwbGl0ID0gJyAnKSkpCmxlbmd0aChtZW1icmFuZUdlbmVzKQpgYGAKUHJvdGVpbnMgd2l0aCBleHRyYWNlbGx1bGFyIGRvbWFpbiB3ZXJlIGZ1cnRoZXIgc2VsZWN0ZWQgZnJvbSBhbGwgZ2VuZSBuYW1lcyBmb3IgYm90aCB0cmFuc2NyaXB0b21lIGFuZCBpbW11bm9oaXN0b2NoZW1pY2FsIGFuYWx5c2VzIGJhc2VkIG9uIHRoZSBkZXNjcmlwdGlvbiBvZiB0aGUgY2F0ZWdvcnkgYFRvcG9sb2dpY2FsLmRvbWFpbmAgZnJvbSBVbmlwcm90IGRhdGFiYXNlLiBGaXJzdCwgd2Ugc2VsZWN0IHRoZSBwcm90ZWlucyBmcm9tIHRoZSB1bmlwcm90IGRhdGEgaWYgdGhleSBoYXZlIGBFeHRyYWNlbGx1bGFyYCBpbiB0aGUgdmFsdWUgb2YgdGhlIGBUb3BvbG9naWNhbC5kb21haW5gIGNvbHVtbiwgdGhlbiBnZXQgdGhlaXIgZ2VuZSBuYW1lcyAoaW5jbHVkaW5nIHN5bm9ueW1zKS4KCmBgYHtyfQpwcm90ZWluc1dpdGhFeHRyYWNlbGx1YXJUb3BvRG9tYWluID0gdW5pcHJvdFtncmVwKCdFeHRyYWNlbGx1bGFyLicsIHVuaXByb3QkVG9wb2xvZ2ljYWwuZG9tYWluKSwgXQpnZW5lc0ZvckV4dHJhY2VsbHVhclByb3RlaW5zID0gdW5pcXVlKHVubGlzdChzdHJzcGxpdChwcm90ZWluc1dpdGhFeHRyYWNlbGx1YXJUb3BvRG9tYWluJEdlbmUubmFtZXMsIHNwbGl0ID0gJyAnKSkpCmxlbmd0aChnZW5lc0ZvckV4dHJhY2VsbHVhclByb3RlaW5zKQpgYGAKIyMjIyMgMi4yLiBDYW5jZXIgZGF0YQpDYW5jZXIgZGF0YSBhcmUgcHJvY2Vzc2VkIHRvIG9idGFpbiB0aGUgbWVhbiBzdGFpbmluZyBzY29yZS4gTnVtZXJpY2FsIHZhbHVlcyBhcmUgYXNzaWduZWQgdG8gc3RhaW5pbmcgbGV2ZWxzLGBbSGlnaCBNZWRpdWVtIExvdyBOb3QuZGV0ZWN0ZWRdID0gWzMgMiAxIDBdO2AgdGhpcyBhc3NpZ25tZW50IGNhbiBiZSBtb2RpZmllZC4gCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpzY29yZXMgPSBkaWFnKGMoMywgMiwgMSwgMCkpCmNhbmNlclRpc3N1ZUlIQyRtZWFuU2NvcmVzID0gcm93TWVhbnMoYXMubWF0cml4KGNhbmNlclRpc3N1ZUlIQ1ssIGMoIkhpZ2giLCAiTWVkaXVtIiwgIkxvdyIsICJOb3QuZGV0ZWN0ZWQiKV0pICUqJSBzY29yZXMpCmBgYAoKQnkgaW5zcGVjdGluZyB0aGUgbnVtYmVyIG9mIHVuaXF1ZSBnZW5lIG5hbWVzLCB3ZSBmb3VuZCB0aGUgbGVuZ3RoIG9mIHVuaXF1ZSBnZW5lIG5hbWVzIGlzIHNob3J0ZXIgdGhhbiB0aGUgc2NvcmUgbGlzdC4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmNhbmNlclRpc3N1ZUlIQ1tkdXBsaWNhdGVkKGNhbmNlclRpc3N1ZUlIQyRHZW5lLm5hbWUpLF0kR2VuZS5uYW1lICMgMTMgZHVwbGljYXRlZCBnZW5lIG5hbWVzCmBgYApJbiBhZGRpdGlvbiB0byAxMyByZWNvcmRzIHdpdGggZHVwbGljYXRlZCBnZW5lIG5hbWVzICh3aXRoIGlkZW50aWNhbCBvciBkaWZmZXJlbnQgbWVhbiBJSEMgc2NvcmVzKSwgdGhlcmUgYXJlIGFsc28gNDI5MiBOQSB2YWx1ZXMuIFRoZXNlIHR3byBkYXRhIHF1YWxpdHkgaXNzdWVzIGNhbiBiZSByZXNvbHZlZCBieSBhZ2dyZWF0aW5nIHRoZSBtZWFuIHNjb3JlcyBieSBnZW5lIG5hbWVzIHdoaWxlIHJlbW92aW5nIE5BIHZhbHVlcyAoZGVmYXVsdCBvcHRpb24pIHRvIGdldCB0aGUgc2FuaXRpemVkIG1lYW4gSUhDIHNjb3JlcyBmb3IgZWFjaCBnZW5lIGZvciBjb2xvcmVjdGFsIGNhbmNlcnMuCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnNhbml0aXplZGNhbmNlclRpc3N1ZUlIQyA9IGFnZ3JlZ2F0ZShtZWFuU2NvcmVzIH4gR2VuZS5uYW1lLCBjYW5jZXJUaXNzdWVJSEMsIG1lYW4pCmdlbmVzU2VsZWN0ZWRGcm9tQ2FuY2VyID0gaW50ZXJzZWN0KGdlbmVzRm9yRXh0cmFjZWxsdWFyUHJvdGVpbnMsIHNhbml0aXplZGNhbmNlclRpc3N1ZUlIQyRHZW5lLm5hbWUpCmdlbmVzU2VsZWN0ZWRGb3JBbmFseXNpcyA9IGludGVyc2VjdChnZW5lc1NlbGVjdGVkRnJvbUNhbmNlciwgZ2VuZXNJbkV2cGVkaWEpIAojIyBnZW5lc1NlbGVjdGVkRm9yQW5hbHlzaXMgPSBnZW5lc1NlbGVjdGVkRnJvbUNhbmNlcgpgYGAKIyMjIyMgMi4zLiBOb3JtYWwgdGlzc3VlIGRhdGEKV2UgcGVyZm9tZSB0aGUgc2ltaWxhciBwcm9jZXNzaW5nIHdpdGggdGhlIG5vcm1hbCB0aXNzdWUgZGF0YS4gQXMgdGhlIGRhdGFzZXQgaXMgbGFyZ2UsIHdlIGxpbWl0IGdlbmVzIHVzZWQgaW4gdGhlIHR1bW9yLiAKCmBgYHtyfQpnZW5lc0luTm9ybWFsVGlzc3VlSUhDID0gdW5pcXVlKG5vcm1hbFRpc3N1ZUlIQyRHZW5lLm5hbWUpCmdlbmVzU2VsZWN0ZWRGb3JBbmFseXNpcyA9IGludGVyc2VjdChnZW5lc1NlbGVjdGVkRm9yQW5hbHlzaXMsIGdlbmVzSW5Ob3JtYWxUaXNzdWVJSEMpCnNlbGVjdGVkTm9ybWFsVGlzc3VlSUhDUmVjb3JkcyA9IG5vcm1hbFRpc3N1ZUlIQ1tub3JtYWxUaXNzdWVJSEMkR2VuZS5uYW1lICVpbiUgZ2VuZXNTZWxlY3RlZEZvckFuYWx5c2lzLF0KYGBgCk5vcm1hbCB0aXNzdWUgZGF0YSBoYXZlIG11bHRpcGxlIElIQyBzY29yZXMgZm9yIGEgZ2l2ZW4gZ2VuZSwgYXMgdGhlIGdlbmUgaXMgcHJlc2VudCBpbiBkaWZmZXJlbnQgdHlwZXMgb2YgdGlzc3VlLiBXZSByZWFycmFuZ2Ugbm9ybWFsIHRpc3N1ZSBkYXRhIHRvIGNvdW50IHRoZSBvY2N1cmVuY2Ugb2YgdGhlIElIQyBzY29yZSBmb3IgZWFjaCBnZW5lLgoKYGBge3J9CnNlbGVjdGVkTm9ybWFsVGlzc3VlSUhDID0gdGFibGUoc2VsZWN0ZWROb3JtYWxUaXNzdWVJSENSZWNvcmRzJEdlbmUubmFtZSwgc2VsZWN0ZWROb3JtYWxUaXNzdWVJSENSZWNvcmRzJExldmVsKQpzZWxlY3RlZE5vcm1hbFRpc3N1ZUlIQy5tZWFuU2NvcmVzID0gcm93TWVhbnMoYXMubWF0cml4KHNlbGVjdGVkTm9ybWFsVGlzc3VlSUhDWywgYygiSGlnaCIsICJNZWRpdW0iLCAiTG93IiwgIk5vdCBkZXRlY3RlZCIpXSkgJSolIHNjb3JlcykKYGBgCgojIyMjIDMuIFBsb3R0aW5nIGRhdGEKIyMjIyMgMy4xLiBMaW5lYXIgZGF0YSBzY2FsaW5nClRoaXMgcm91dGluZSB1c2VzIGEgcmVndWxhciBub21yYWxpemF0aW9uIGBbbWluIG1heF0gPSAgWzAgMV1gLiAKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShzY2FsZXMpCnNjYWxlZFR1bW9yVGlzc3VlSUhDU2NvcmVzID0gcmVzY2FsZShzYW5pdGl6ZWRjYW5jZXJUaXNzdWVJSEMkbWVhblNjb3JlcykKbmFtZXMoc2NhbGVkVHVtb3JUaXNzdWVJSENTY29yZXMpID0gc2FuaXRpemVkY2FuY2VyVGlzc3VlSUhDJEdlbmUubmFtZQpzY2FsZWRTZWxlY3RlZFR1bW9ySUhDU2NvcmVzID0gc2NhbGVkVHVtb3JUaXNzdWVJSENTY29yZXNbZ2VuZXNTZWxlY3RlZEZvckFuYWx5c2lzXQpzY2FsZWROb3JtYWxUaXNzdWVJSENTY29yZXMgPSByZXNjYWxlKHNlbGVjdGVkTm9ybWFsVGlzc3VlSUhDLm1lYW5TY29yZXMpCnNjYWxlZE5vcm1hbFRpc3N1ZUlIQ1Njb3JlcyA9IHNjYWxlZE5vcm1hbFRpc3N1ZUlIQ1Njb3Jlc1tnZW5lc1NlbGVjdGVkRm9yQW5hbHlzaXNdCmBgYApQbG90dGluZyBkYXRhOgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dyZXBlbCkKbGlicmFyeShzY2ljbykKCmQgPSBkYXRhLmZyYW1lKHggPSBzY2FsZWRTZWxlY3RlZFR1bW9ySUhDU2NvcmVzLCB5ID0gc2NhbGVkTm9ybWFsVGlzc3VlSUhDU2NvcmVzLCB6ID0gc2NhbGVkU2VsZWN0ZWRUdW1vcklIQ1Njb3JlcyAtIHNjYWxlZE5vcm1hbFRpc3N1ZUlIQ1Njb3JlcykKY29sb3JtYXAgPSBzY2ljbygyNTYsIHBhbGV0dGUgPSAidmlrIiwgZGlyZWN0aW9uPTEpCgpnZ3Bsb3QoZCwgYWVzKHgsIHkpKSArIGdlb21fcG9pbnQoYWVzKGNvbG9yID0geikpICsgCiAgbGFicyh4ID0gIlN0YWluaW5nIHNjb3JlIGluIENSQyB0aXNzdWUgKGEudS4pIiwgeSA9ICJTdGFpbmluZyBzY29yZSBpbiBub3JtYWwgdGlzc3VlIChhLnUuKSIpICsgCiAgbGFicyhjb2xvdXIgPSAiRGlmZmVyZW5jZVxuW3R1bW9yIC0gbm9ybWFsXSIpICsgCiAgc2NhbGVfY29sb3VyX2dyYWRpZW50bihsaW1pdHM9YygtMSwgMSksIGNvbG91cnMgPSBjb2xvcm1hcCkgKyAKICBnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsPWlmZWxzZSh6ID4gMS41LCBnZW5lc1NlbGVjdGVkRm9yQW5hbHlzaXMsJycpKSwgc2l6ZSA9IDMpICsKICB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNSksIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpKSArCiAgY29vcmRfZml4ZWQoMSkKYGBgCiMjIyMjIDMuMiBaLXNjb3JlClRoaXMgcm91dGluZSBzY2FsZXMgdGhlIGRhdGEgdG8gbm9ybWFsIGRpc3RyaWJ1dGlvbi4KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbXUgPSBtZWFuKHNhbml0aXplZGNhbmNlclRpc3N1ZUlIQyRtZWFuU2NvcmVzKQpzaWdtYSA9IHNkKHNhbml0aXplZGNhbmNlclRpc3N1ZUlIQyRtZWFuU2NvcmVzKQpzY2FsZWRUdW1vclRpc3N1ZVpTY29yZXMgPSAoc2FuaXRpemVkY2FuY2VyVGlzc3VlSUhDJG1lYW5TY29yZXMgLSBtdSkvc2lnbWEgCm5hbWVzKHNjYWxlZFR1bW9yVGlzc3VlWlNjb3JlcykgPSBzYW5pdGl6ZWRjYW5jZXJUaXNzdWVJSEMkR2VuZS5uYW1lCnNjYWxlZFNlbGVjdGVkVHVtb3JaU2NvcmVzID0gc2NhbGVkVHVtb3JUaXNzdWVaU2NvcmVzW2dlbmVzU2VsZWN0ZWRGb3JBbmFseXNpc10KCm11ID0gbWVhbihzZWxlY3RlZE5vcm1hbFRpc3N1ZUlIQy5tZWFuU2NvcmVzKQpzaWdtYSA9IHNkKHNlbGVjdGVkTm9ybWFsVGlzc3VlSUhDLm1lYW5TY29yZXMpCgpzY2FsZWROb3JtYWxUaXNzdWVaU2NvcmVzID0gKHNlbGVjdGVkTm9ybWFsVGlzc3VlSUhDLm1lYW5TY29yZXMgLSBtdSkvc2lnbWEKc2NhbGVkTm9ybWFsVGlzc3VlWlNjb3JlcyA9IHNjYWxlZE5vcm1hbFRpc3N1ZVpTY29yZXNbZ2VuZXNTZWxlY3RlZEZvckFuYWx5c2lzXQpgYGAKUGxvdHRpbmcgZGF0YToKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZHogPSBkYXRhLmZyYW1lKHggPSBzY2FsZWRTZWxlY3RlZFR1bW9yWlNjb3JlcywgeSA9IHNjYWxlZE5vcm1hbFRpc3N1ZVpTY29yZXMsIHogPSBzY2FsZWRTZWxlY3RlZFR1bW9yWlNjb3JlcyAtIHNjYWxlZE5vcm1hbFRpc3N1ZVpTY29yZXMpCgpnZ3Bsb3QoZHosIGFlcyh4LCB5KSkgKyBnZW9tX3BvaW50KGFlcyhjb2xvciA9IHopKSArIAogIGxhYnMoeCA9ICJaIHNjb3JlIGluIENSQyB0aXNzdWUgKGEudS4pIiwgeSA9ICJaIHNjb3JlIGluIG5vcm1hbCB0aXNzdWUgKGEudS4pIikgKyAKICBsYWJzKGNvbG91ciA9ICJEaWZmZXJlbmNlXG5bdHVtb3IgLSBub3JtYWxdIikgKyAKICB4bGltKC0xLjUsIDIuNSkgKyB5bGltKC0xLjUsIDIuNSkgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudG4obGltaXRzPWMoLTMsIDMpLCBjb2xvdXJzID0gY29sb3JtYXApICsgCiAgZ2VvbV90ZXh0X3JlcGVsKGFlcyhsYWJlbD1pZmVsc2UoeiA+IDEuNSxnZW5lc1NlbGVjdGVkRm9yQW5hbHlzaXMsJycpKSwgc2l6ZSA9IDMpICsgCiAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTUpLCBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSkrCiAgY29vcmRfZml4ZWQoMSkKYGBg