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)) # Clean all variables

cancerName = '^colorectal' # Cancer type
setwd("/Volumes/Work/Markerwork/") # set the working directory)
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)
# Choose only the target cancer type
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.1. Extract genes for processing

We extract unique gene names associated with membrane proteins from UniProt. The total number of genes is checked.

membraneGenes = unique(unlist(strsplit(uniprot$Gene.names, split = ' ')))
length(membraneGenes)
[1] 6566

Proteins with extracellular domain were further selected from all gene names for both transcriptome and immunohistochemical analyses based on the description of the category Topological.domain from Uniprot database. First, we select the proteins from the uniprot data if they have Extracellular in the value of the Topological.domain column, then get their gene names (including synonyms).

proteinsWithExtracelluarTopoDomain = uniprot[grep('Extracellular.', uniprot$Topological.domain), ]
genesForExtracelluarProteins = unique(unlist(strsplit(proteinsWithExtracelluarTopoDomain$Gene.names, split = ' ')))
length(genesForExtracelluarProteins)
[1] 2913
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 # 13 duplicated gene names
 [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) 
## genesSelectedForAnalysis = genesSelectedFromCancer
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