Background
Duguet et al. 2017 compared the proteomes of mouse regulatory T cells (Treg) and conventional T cells (Tconv) in order to discover differentially regulated proteins between these two cell populations. For each biological repeat the proteomes were extracted for both Treg and Tconv cell pools, which were purified by flow cytometry. The data in data/quantification/mouseTcell on the pdaData repository are a subset of the data PXD004436 on PRIDE.
We will use a subset of the data with a randomized complete block (RCB) design, i.e. the dataset consists of four mice for which the proteome of both conventional and regulatory T cells are assessed.
Data
We first import the peptides.txt file. This is the file that contains your peptide-level intensities. For a MaxQuant search [6], this peptides.txt file can be found by default in the “path_to_raw_files/combined/txt/” folder from the MaxQuant output, with “path_to_raw_files” the folder where raw files were saved. In this tutorial, we will use a MaxQuant peptides file from MaxQuant that can be found in the data tree of the SGA2020 github repository https://github.com/statOmics/SGA2020/tree/data/quantification/mouseTcell .
To import the data we use the QFeatures
package.
We generate the object peptideRawFile with the path to the peptideRaws.txt file. Using the grepEcols
function, we find the columns that contain the expression data of the peptideRaws in the peptideRaws.txt file.
library(tidyverse)
library(limma)
library(QFeatures)
library(msqrob2)
library(plotly)
peptidesFile <- "https://raw.githubusercontent.com/statOmics/SGA2020/data/quantification/mouseTcell/peptidesRCB.txt"
ecols <- MSnbase::grepEcols(
peptidesFile,
"Intensity ",
split = "\t")
pe <- readQFeatures(
table = peptidesFile,
fnames = 1,
ecol = ecols,
name = "peptideRaw", sep="\t")
pe
## An instance of class QFeatures containing 1 assays:
## [1] peptideRaw: SummarizedExperiment with 55814 rows and 8 columns
## class: SummarizedExperiment
## dim: 55814 8
## metadata(0):
## assays(1): ''
## rownames(55814): AAAAAAAAAAGAAGGR AAAAAAAAAAGDSDSWDADTFSMEDPVRK ...
## YYYDGDMICK YYYDKNIIHK
## rowData names(74): Sequence N.term.cleavage.window ...
## Oxidation..M..site.IDs MS.MS.Count
## colnames(8): Intensity.Tconv.M12_2 Intensity.Tconv.M12_3 ...
## Intensity.Treg.M5_inj1 Intensity.Treg.M6_inj1
## colData names(0):
We will make use from data wrangling functionalities from the tidyverse package. The %>% operator allows us to pipe the output of one function to the next function.
colData(pe)$celltype <- substr(
colnames(pe[["peptideRaw"]]),
11,
14) %>%
unlist %>%
as.factor
colData(pe)$mouse <- pe[[1]] %>%
colnames %>%
strsplit(split="[.]") %>%
sapply(function(x) x[3]) %>%
as.factor
We calculate how many non zero intensities we have per peptide and this will be useful for filtering.
rowData(pe[["peptideRaw"]])$nNonZero <- rowSums(assay(pe[["peptideRaw"]]) > 0)
Peptides with zero intensities are missing peptides and should be represent with a NA
value rather than 0
.
pe <- zeroIsNA(pe, "peptideRaw") # convert 0 to NA
Data exploration
We can inspect the missingness in our data with the plotNA()
function provided with MSnbase
. 38% of all peptide intensities are missing and for some peptides we do not even measure a signal in any sample. The missingness is similar across samples.
MSnbase::plotNA(assay(pe[["peptideRaw"]])) +
xlab("Peptide index (ordered by data completeness)")
Preprocessing
This section preforms standard preprocessing for the peptide data. This include log transformation, filtering and summarisation of the data.
Filtering
Handling overlapping protein groups
In our approach a peptide can map to multiple proteins, as long as there is none of these proteins present in a smaller subgroup.
pe <- filterFeatures(pe,
~ Proteins %in% smallestUniqueGroups(rowData(pe[["peptideLog"]])$Proteins)
)
Remove reverse sequences (decoys) and contaminants
We now remove the contaminants, peptides that map to decoy sequences, and proteins which were only identified by peptides with modifications.
pe <- filterFeatures(pe, ~ Reverse != "+")
pe <- filterFeatures(pe, ~ Potential.contaminant != "+")
Remove peptides of proteins that were only identified with modified peptides
I will skip this step for the moment. Large protein groups file needed for this.
Drop peptides that were only identified in one sample
We keep peptides that were observed at last twice.
pe <- filterFeatures(pe, ~ nNonZero >= 2)
nrow(pe[["peptideLog"]])
## [1] 44449
We keep 44449 peptides after filtering.
Quantile normalize the data
pe <- normalize(pe, i = "peptideLog", method = "quantiles", name = "peptideNorm")
Explore quantile normalized data
After quantile normalisation the density curves for all samples coincide.
limma::plotDensities(assay(pe[["peptideNorm"]]))
This is more clearly seen is a boxplot.
boxplot(assay(pe[["peptideNorm"]]), col = palette()[-1],
main = "Peptide distribtutions after normalisation", ylab = "intensity")
We can visualize our data using a Multi Dimensional Scaling plot, eg. as provided by the limma
package.
limma::plotMDS(assay(pe[["peptideNorm"]]), col = as.numeric(colData(pe)$celltype))
The first axis in the plot is showing the leading log fold changes (differences on the log scale) between the samples.
Summarization to protein level
We use robust summarization in aggregateFeatures. This is the default workflow of aggregateFeatures so you do not have to specifiy the argument fun
. However, because we compare methods we have included the fun
argument to show the summarization method explicitely.
pe <- aggregateFeatures(pe,
i = "peptideNorm",
fcol = "Proteins",
na.rm = TRUE,
name = "proteinRobust",
fun = MsCoreUtils::robustSummary)
## Your quantitative and row data contain missing values. Please read the
## relevant section(s) in the aggregateFeatures manual page regarding the
## effects of missing values on data aggregation.
plotMDS(assay(pe[["proteinRobust"]]), col = as.numeric(colData(pe)$celltype))
Data Analysis
Estimation
We model the protein level expression values using msqrob
. By default msqrob2
estimates the model parameters using robust regression.
pe <- msqrob(
object = pe,
i = "proteinRobust",
formula = ~ celltype + mouse)
Inference
First, we extract the parameter names of the model.
getCoef(rowData(pe[["proteinRobust"]])$msqrobModels[[1]])
## (Intercept) celltypeTreg mouseM12_3 mouseM5_inj1 mouseM6_inj1
## 20.31055846 0.18119940 0.14049510 -0.05802897 -0.25319752
Spike-in celltype a is the reference class. So the mean log2 expression for samples from celltype a is ‘(Intercept). The mean log2 expression for samples from celltype B is’(Intercept)+celltypeTreg’. Hence, the average log2 fold change between celltype b and celltype a is modelled using the parameter ‘celltypeTreg’. Thus, we assess the contrast ‘celltypeTreg=0’ with our statistical test.
L <- makeContrast("celltypeTreg=0", parameterNames = c("celltypeTreg"))
pe <- hypothesisTest(object = pe, i = "proteinRobust", contrast = L)
Plots
Volcano-plot
volcano <- ggplot(rowData(pe[["proteinRobust"]])$celltypeTreg,
aes(x = logFC, y = -log10(pval), color = adjPval < 0.05)) +
geom_point(cex = 2.5) +
scale_color_manual(values = alpha(c("black", "red"), 0.5)) + theme_minimal()
volcano
Heatmap
We first select the names of the proteins that were declared signficant.
sigNames <- rowData(pe[["proteinRobust"]])$celltypeTreg %>%
rownames_to_column("proteinRobust") %>%
filter(adjPval<0.05) %>%
pull(proteinRobust)
heatmap(assay(pe[["proteinRobust"]])[sigNames, ])
There are 125 proteins significantly differentially expressed at the 5% FDR level.
rowData(pe[["proteinRobust"]])$celltypeTreg %>%
filter(adjPval<0.05)
Detail plots
for (protName in sigNames[1:5])
{
pePlot <- pe[protName, , c("peptideNorm","proteinRobust")]
pePlotDf <- data.frame(longFormat(pePlot))
pePlotDf$assay <- factor(pePlotDf$assay,
levels = c("peptideNorm", "proteinRobust"))
pePlotDf$celltype <- as.factor(colData(pePlot)[pePlotDf$colname, "celltype"])
# plotting
p1 <- ggplot(data = pePlotDf,
aes(x = colname, y = value, group = rowname)) +
geom_line() + geom_point() + theme_minimal() +
facet_grid(~assay) + ggtitle(protName)
print(p1)
# plotting 2
p2 <- ggplot(pePlotDf, aes(x = colname, y = value, fill = celltype)) +
geom_boxplot(outlier.shape = NA) + geom_point(position = position_jitter(width = .1),
aes(shape = rowname)) +
scale_shape_manual(values = 1:nrow(pePlotDf)) +
labs(title = protName, x = "sample", y = "peptide intensity (log2)") + theme_minimal()
facet_grid(~assay)
print(p2)
}
LS0tCnRpdGxlOiAiUHJvdGVvbWljcyBkYXRhIGFuYWx5c2lzOiBtb3VzZSBUY2VsbCBleGFtcGxlIHdpdGggUkNCIGRlc2lnbiIKYXV0aG9yOiAiTGlldmVuIENsZW1lbnQiCmRhdGU6ICJzdGF0T21pY3MsIEdoZW50IFVuaXZlcnNpdHkgKGh0dHBzOi8vc3RhdG9taWNzLmdpdGh1Yi5pbykiCm91dHB1dDoKICAgIGh0bWxfZG9jdW1lbnQ6CiAgICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgICAgdGhlbWU6IGNvc21vCiAgICAgIHRvYzogdHJ1ZQogICAgICB0b2NfZmxvYXQ6IHRydWUKICAgICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKLS0tCiMgQmFja2dyb3VuZApEdWd1ZXQgZXQgYWwuIDIwMTcgY29tcGFyZWQgdGhlIHByb3Rlb21lcyBvZiBtb3VzZSByZWd1bGF0b3J5IFQgY2VsbHMgKFRyZWcpIGFuZCBjb252ZW50aW9uYWwgVCBjZWxscyAoVGNvbnYpIGluIG9yZGVyIHRvIGRpc2NvdmVyIGRpZmZlcmVudGlhbGx5IHJlZ3VsYXRlZCBwcm90ZWlucyBiZXR3ZWVuIHRoZXNlIHR3byBjZWxsIHBvcHVsYXRpb25zLiBGb3IgZWFjaCBiaW9sb2dpY2FsIHJlcGVhdCB0aGUgcHJvdGVvbWVzIHdlcmUgZXh0cmFjdGVkIGZvciBib3RoIFRyZWcgYW5kIFRjb252IGNlbGwgcG9vbHMsIHdoaWNoIHdlcmUgcHVyaWZpZWQgYnkgZmxvdyBjeXRvbWV0cnkuIFRoZSBkYXRhIGluIGRhdGEvcXVhbnRpZmljYXRpb24vbW91c2VUY2VsbCBvbiB0aGUgcGRhRGF0YSByZXBvc2l0b3J5IGFyZSBhIHN1YnNldCBvZiB0aGUgZGF0YSBbUFhEMDA0NDM2XShodHRwczovL3d3dy5lYmkuYWMudWsvcHJpZGUvYXJjaGl2ZS9wcm9qZWN0cy9QWEQwMDQ0MzYpIG9uIFBSSURFLgoKV2Ugd2lsbCB1c2UgYSBzdWJzZXQgb2YgdGhlIGRhdGEgd2l0aCBhIHJhbmRvbWl6ZWQgY29tcGxldGUgYmxvY2sgKFJDQikgZGVzaWduLCBpLmUuIHRoZSBkYXRhc2V0IGNvbnNpc3RzIG9mIGZvdXIgbWljZSBmb3Igd2hpY2ggdGhlIHByb3Rlb21lIG9mIGJvdGggY29udmVudGlvbmFsIGFuZCByZWd1bGF0b3J5IFQgY2VsbHMgYXJlIGFzc2Vzc2VkLgoKCiMgRGF0YQoKV2UgZmlyc3QgaW1wb3J0IHRoZSBwZXB0aWRlcy50eHQgZmlsZS4gVGhpcyBpcyB0aGUgZmlsZSB0aGF0IGNvbnRhaW5zIHlvdXIgcGVwdGlkZS1sZXZlbCBpbnRlbnNpdGllcy4gRm9yIGEgTWF4UXVhbnQgc2VhcmNoIFs2XSwgdGhpcyBwZXB0aWRlcy50eHQgZmlsZSBjYW4gYmUgZm91bmQgYnkgZGVmYXVsdCBpbiB0aGUgInBhdGhfdG9fcmF3X2ZpbGVzL2NvbWJpbmVkL3R4dC8iIGZvbGRlciBmcm9tIHRoZSBNYXhRdWFudCBvdXRwdXQsIHdpdGggInBhdGhfdG9fcmF3X2ZpbGVzIiB0aGUgZm9sZGVyIHdoZXJlIHJhdyBmaWxlcyB3ZXJlIHNhdmVkLiBJbiB0aGlzIHR1dG9yaWFsLCB3ZSB3aWxsIHVzZSBhIE1heFF1YW50IHBlcHRpZGVzIGZpbGUgZnJvbSBNYXhRdWFudCB0aGF0IGNhbiBiZSBmb3VuZCBpbiB0aGUgZGF0YSB0cmVlIG9mIHRoZSBTR0EyMDIwIGdpdGh1YiByZXBvc2l0b3J5IGh0dHBzOi8vZ2l0aHViLmNvbS9zdGF0T21pY3MvU0dBMjAyMC90cmVlL2RhdGEvcXVhbnRpZmljYXRpb24vbW91c2VUY2VsbCAuCgpUbyBpbXBvcnQgdGhlIGRhdGEgd2UgdXNlIHRoZSBgUUZlYXR1cmVzYCBwYWNrYWdlLgoKV2UgZ2VuZXJhdGUgdGhlIG9iamVjdCBwZXB0aWRlUmF3RmlsZSB3aXRoIHRoZSBwYXRoIHRvIHRoZSBwZXB0aWRlUmF3cy50eHQgZmlsZS4KVXNpbmcgdGhlIGBncmVwRWNvbHNgIGZ1bmN0aW9uLCB3ZSBmaW5kIHRoZSBjb2x1bW5zIHRoYXQgY29udGFpbiB0aGUgZXhwcmVzc2lvbgpkYXRhIG9mIHRoZSBwZXB0aWRlUmF3cyBpbiB0aGUgcGVwdGlkZVJhd3MudHh0IGZpbGUuCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobGltbWEpCmxpYnJhcnkoUUZlYXR1cmVzKQpsaWJyYXJ5KG1zcXJvYjIpCmxpYnJhcnkocGxvdGx5KQoKcGVwdGlkZXNGaWxlIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1NHQTIwMjAvZGF0YS9xdWFudGlmaWNhdGlvbi9tb3VzZVRjZWxsL3BlcHRpZGVzUkNCLnR4dCIKCmVjb2xzIDwtIE1TbmJhc2U6OmdyZXBFY29scygKICBwZXB0aWRlc0ZpbGUsCiAgIkludGVuc2l0eSAiLAogIHNwbGl0ID0gIlx0IikKCnBlIDwtIHJlYWRRRmVhdHVyZXMoCiAgdGFibGUgPSBwZXB0aWRlc0ZpbGUsCiAgZm5hbWVzID0gMSwKICBlY29sID0gZWNvbHMsCiAgbmFtZSA9ICJwZXB0aWRlUmF3Iiwgc2VwPSJcdCIpCgpwZQpwZVtbInBlcHRpZGVSYXciXV0KYGBgCgpXZSB3aWxsIG1ha2UgdXNlIGZyb20gZGF0YSB3cmFuZ2xpbmcgZnVuY3Rpb25hbGl0aWVzIGZyb20gdGhlIHRpZHl2ZXJzZSBwYWNrYWdlLgpUaGUgJT4lIG9wZXJhdG9yIGFsbG93cyB1cyB0byBwaXBlIHRoZSBvdXRwdXQgb2Ygb25lIGZ1bmN0aW9uIHRvIHRoZSBuZXh0IGZ1bmN0aW9uLgoKYGBge3J9CmNvbERhdGEocGUpJGNlbGx0eXBlIDwtIHN1YnN0cigKICBjb2xuYW1lcyhwZVtbInBlcHRpZGVSYXciXV0pLAogIDExLAogIDE0KSAlPiUKICB1bmxpc3QgJT4lCiAgYXMuZmFjdG9yCgpjb2xEYXRhKHBlKSRtb3VzZSA8LSBwZVtbMV1dICU+JQogIGNvbG5hbWVzICU+JQogIHN0cnNwbGl0KHNwbGl0PSJbLl0iKSAgJT4lCiAgc2FwcGx5KGZ1bmN0aW9uKHgpIHhbM10pICU+JQogIGFzLmZhY3RvcgpgYGAKCgpXZSBjYWxjdWxhdGUgaG93IG1hbnkgbm9uIHplcm8gaW50ZW5zaXRpZXMgd2UgaGF2ZSBwZXIgcGVwdGlkZSBhbmQgdGhpcwp3aWxsIGJlIHVzZWZ1bCBmb3IgZmlsdGVyaW5nLgoKYGBge3J9CnJvd0RhdGEocGVbWyJwZXB0aWRlUmF3Il1dKSRuTm9uWmVybyA8LSByb3dTdW1zKGFzc2F5KHBlW1sicGVwdGlkZVJhdyJdXSkgPiAwKQpgYGAKCgpQZXB0aWRlcyB3aXRoIHplcm8gaW50ZW5zaXRpZXMgYXJlIG1pc3NpbmcgcGVwdGlkZXMgYW5kIHNob3VsZCBiZSByZXByZXNlbnQKd2l0aCBhIGBOQWAgdmFsdWUgcmF0aGVyIHRoYW4gYDBgLgpgYGB7cn0KcGUgPC0gemVyb0lzTkEocGUsICJwZXB0aWRlUmF3IikgIyBjb252ZXJ0IDAgdG8gTkEKYGBgCgoKIyMgRGF0YSBleHBsb3JhdGlvbgoKV2UgY2FuIGluc3BlY3QgdGhlIG1pc3NpbmduZXNzIGluIG91ciBkYXRhIHdpdGggdGhlIGBwbG90TkEoKWAgZnVuY3Rpb24KcHJvdmlkZWQgd2l0aCBgTVNuYmFzZWAuCmByIGZvcm1hdChtZWFuKGlzLm5hKGFzc2F5KHBlW1sicGVwdGlkZVJhdyJdXSkpKSoxMDAsZGlnaXRzPTIpYCUgb2YgYWxsIHBlcHRpZGUKaW50ZW5zaXRpZXMgYXJlIG1pc3NpbmcgYW5kIGZvciBzb21lIHBlcHRpZGVzIHdlIGRvIG5vdCBldmVuIG1lYXN1cmUgYSBzaWduYWwKaW4gYW55IHNhbXBsZS4gVGhlIG1pc3NpbmduZXNzIGlzIHNpbWlsYXIgYWNyb3NzIHNhbXBsZXMuCgoKYGBge3J9Ck1TbmJhc2U6OnBsb3ROQShhc3NheShwZVtbInBlcHRpZGVSYXciXV0pKSArCiAgeGxhYigiUGVwdGlkZSBpbmRleCAob3JkZXJlZCBieSBkYXRhIGNvbXBsZXRlbmVzcykiKQpgYGAKCiMgUHJlcHJvY2Vzc2luZwoKVGhpcyBzZWN0aW9uIHByZWZvcm1zIHN0YW5kYXJkIHByZXByb2Nlc3NpbmcgZm9yIHRoZSBwZXB0aWRlIGRhdGEuIFRoaXMKaW5jbHVkZSBsb2cgdHJhbnNmb3JtYXRpb24sIGZpbHRlcmluZyBhbmQgc3VtbWFyaXNhdGlvbiBvZiB0aGUgZGF0YS4KCiMjIExvZyB0cmFuc2Zvcm0gdGhlIGRhdGEKCmBgYHtyfQpwZSA8LSBsb2dUcmFuc2Zvcm0ocGUsIGJhc2UgPSAyLCBpID0gInBlcHRpZGVSYXciLCBuYW1lID0gInBlcHRpZGVMb2ciKQpsaW1tYTo6cGxvdERlbnNpdGllcyhhc3NheShwZVtbInBlcHRpZGVMb2ciXV0pKQpgYGAKCgojIyBGaWx0ZXJpbmcKCiMjIyBIYW5kbGluZyBvdmVybGFwcGluZyBwcm90ZWluIGdyb3VwcwpJbiBvdXIgYXBwcm9hY2ggYSBwZXB0aWRlIGNhbiBtYXAgdG8gbXVsdGlwbGUgcHJvdGVpbnMsIGFzIGxvbmcgYXMgdGhlcmUgaXMKbm9uZSBvZiB0aGVzZSBwcm90ZWlucyBwcmVzZW50IGluIGEgc21hbGxlciBzdWJncm91cC4KCmBgYHtyfQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSwKICB+IFByb3RlaW5zICVpbiUgc21hbGxlc3RVbmlxdWVHcm91cHMocm93RGF0YShwZVtbInBlcHRpZGVMb2ciXV0pJFByb3RlaW5zKQopCmBgYAoKIyMjIFJlbW92ZSByZXZlcnNlIHNlcXVlbmNlcyAoZGVjb3lzKSBhbmQgY29udGFtaW5hbnRzCgpXZSBub3cgcmVtb3ZlIHRoZSBjb250YW1pbmFudHMsIHBlcHRpZGVzIHRoYXQgbWFwIHRvIGRlY295IHNlcXVlbmNlcywgYW5kIHByb3RlaW5zCndoaWNoIHdlcmUgb25seSBpZGVudGlmaWVkIGJ5IHBlcHRpZGVzIHdpdGggbW9kaWZpY2F0aW9ucy4KCmBgYHtyfQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSwgfiBSZXZlcnNlICE9ICIrIikKcGUgPC0gZmlsdGVyRmVhdHVyZXMocGUsIH4gUG90ZW50aWFsLmNvbnRhbWluYW50ICE9ICIrIikKYGBgCgojIyMgUmVtb3ZlIHBlcHRpZGVzIG9mIHByb3RlaW5zIHRoYXQgd2VyZSBvbmx5IGlkZW50aWZpZWQgd2l0aCBtb2RpZmllZCBwZXB0aWRlcwoKSSB3aWxsIHNraXAgdGhpcyBzdGVwIGZvciB0aGUgbW9tZW50LiBMYXJnZSBwcm90ZWluIGdyb3VwcyBmaWxlIG5lZWRlZCBmb3IgdGhpcy4KCiMjIyBEcm9wIHBlcHRpZGVzIHRoYXQgd2VyZSBvbmx5IGlkZW50aWZpZWQgaW4gb25lIHNhbXBsZQoKV2Uga2VlcCBwZXB0aWRlcyB0aGF0IHdlcmUgb2JzZXJ2ZWQgYXQgbGFzdCB0d2ljZS4KCmBgYHtyfQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSwgfiBuTm9uWmVybyA+PSAyKQpucm93KHBlW1sicGVwdGlkZUxvZyJdXSkKYGBgCgpXZSBrZWVwIGByIG5yb3cocGVbWyJwZXB0aWRlTG9nIl1dKWAgcGVwdGlkZXMgYWZ0ZXIgZmlsdGVyaW5nLgoKIyMgUXVhbnRpbGUgbm9ybWFsaXplIHRoZSBkYXRhCmBgYHtyfQpwZSA8LSBub3JtYWxpemUocGUsIGkgPSAicGVwdGlkZUxvZyIsIG1ldGhvZCA9ICJxdWFudGlsZXMiLCBuYW1lID0gInBlcHRpZGVOb3JtIikKYGBgCgoKIyMgRXhwbG9yZSBxdWFudGlsZSBub3JtYWxpemVkIGRhdGEKCkFmdGVyIHF1YW50aWxlIG5vcm1hbGlzYXRpb24gdGhlIGRlbnNpdHkgY3VydmVzIGZvciBhbGwgc2FtcGxlcyBjb2luY2lkZS4KCmBgYHtyfQpsaW1tYTo6cGxvdERlbnNpdGllcyhhc3NheShwZVtbInBlcHRpZGVOb3JtIl1dKSkKYGBgCgpUaGlzIGlzIG1vcmUgY2xlYXJseSBzZWVuIGlzIGEgYm94cGxvdC4KCmBgYHtyLH0KYm94cGxvdChhc3NheShwZVtbInBlcHRpZGVOb3JtIl1dKSwgY29sID0gcGFsZXR0ZSgpWy0xXSwKICAgICAgIG1haW4gPSAiUGVwdGlkZSBkaXN0cmlidHV0aW9ucyBhZnRlciBub3JtYWxpc2F0aW9uIiwgeWxhYiA9ICJpbnRlbnNpdHkiKQpgYGAKCgpXZSBjYW4gdmlzdWFsaXplIG91ciBkYXRhIHVzaW5nIGEgTXVsdGkgRGltZW5zaW9uYWwgU2NhbGluZyBwbG90LAplZy4gYXMgcHJvdmlkZWQgYnkgdGhlIGBsaW1tYWAgcGFja2FnZS4KCmBgYHtyfQpsaW1tYTo6cGxvdE1EUyhhc3NheShwZVtbInBlcHRpZGVOb3JtIl1dKSwgY29sID0gYXMubnVtZXJpYyhjb2xEYXRhKHBlKSRjZWxsdHlwZSkpCmBgYAoKVGhlIGZpcnN0IGF4aXMgaW4gdGhlIHBsb3QgaXMgc2hvd2luZyB0aGUgbGVhZGluZyBsb2cgZm9sZCBjaGFuZ2VzCihkaWZmZXJlbmNlcyBvbiB0aGUgbG9nIHNjYWxlKSBiZXR3ZWVuIHRoZSBzYW1wbGVzLgoKCiMjIFN1bW1hcml6YXRpb24gdG8gcHJvdGVpbiBsZXZlbAoKV2UgdXNlIHJvYnVzdCBzdW1tYXJpemF0aW9uIGluIGFnZ3JlZ2F0ZUZlYXR1cmVzLiBUaGlzIGlzIHRoZSBkZWZhdWx0IHdvcmtmbG93IG9mIGFnZ3JlZ2F0ZUZlYXR1cmVzIHNvIHlvdSBkbyBub3QgaGF2ZSB0byBzcGVjaWZpeSB0aGUgYXJndW1lbnQgYGZ1bmAuCkhvd2V2ZXIsIGJlY2F1c2Ugd2UgY29tcGFyZSBtZXRob2RzIHdlIGhhdmUgaW5jbHVkZWQgdGhlIGBmdW5gIGFyZ3VtZW50IHRvIHNob3cgdGhlIHN1bW1hcml6YXRpb24gbWV0aG9kIGV4cGxpY2l0ZWx5LgoKYGBge3Isd2FybmluZz1GQUxTRX0KcGUgPC0gYWdncmVnYXRlRmVhdHVyZXMocGUsCiBpID0gInBlcHRpZGVOb3JtIiwKIGZjb2wgPSAiUHJvdGVpbnMiLAogbmEucm0gPSBUUlVFLAogbmFtZSA9ICJwcm90ZWluUm9idXN0IiwKIGZ1biA9IE1zQ29yZVV0aWxzOjpyb2J1c3RTdW1tYXJ5KQpgYGAKCmBgYHtyfQpwbG90TURTKGFzc2F5KHBlW1sicHJvdGVpblJvYnVzdCJdXSksIGNvbCA9IGFzLm51bWVyaWMoY29sRGF0YShwZSkkY2VsbHR5cGUpKQpgYGAKCiMgRGF0YSBBbmFseXNpcwoKIyMgRXN0aW1hdGlvbgoKV2UgbW9kZWwgdGhlIHByb3RlaW4gbGV2ZWwgZXhwcmVzc2lvbiB2YWx1ZXMgdXNpbmcgYG1zcXJvYmAuCkJ5IGRlZmF1bHQgYG1zcXJvYjJgIGVzdGltYXRlcyB0aGUgbW9kZWwgcGFyYW1ldGVycyB1c2luZyByb2J1c3QgcmVncmVzc2lvbi4KCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQpwZSA8LSBtc3Fyb2IoCiAgb2JqZWN0ID0gcGUsCiAgaSA9ICJwcm90ZWluUm9idXN0IiwKICBmb3JtdWxhID0gfiBjZWxsdHlwZSArIG1vdXNlKQpgYGAKCiMjIEluZmVyZW5jZQoKRmlyc3QsIHdlIGV4dHJhY3QgdGhlIHBhcmFtZXRlciBuYW1lcyBvZiB0aGUgbW9kZWwuCmBgYHtyfQpnZXRDb2VmKHJvd0RhdGEocGVbWyJwcm90ZWluUm9idXN0Il1dKSRtc3Fyb2JNb2RlbHNbWzFdXSkKYGBgCgpTcGlrZS1pbiBjZWxsdHlwZSBhIGlzIHRoZSByZWZlcmVuY2UgY2xhc3MuIFNvIHRoZSBtZWFuIGxvZzIgZXhwcmVzc2lvbgpmb3Igc2FtcGxlcyBmcm9tIGNlbGx0eXBlIGEgaXMgJyhJbnRlcmNlcHQpLgpUaGUgbWVhbiBsb2cyIGV4cHJlc3Npb24gZm9yIHNhbXBsZXMgZnJvbSBjZWxsdHlwZSBCIGlzICcoSW50ZXJjZXB0KStjZWxsdHlwZVRyZWcnLgpIZW5jZSwgdGhlIGF2ZXJhZ2UgbG9nMiBmb2xkIGNoYW5nZSBiZXR3ZWVuIGNlbGx0eXBlIGIgYW5kCmNlbGx0eXBlIGEgaXMgbW9kZWxsZWQgdXNpbmcgdGhlIHBhcmFtZXRlciAnY2VsbHR5cGVUcmVnJy4KVGh1cywgd2UgYXNzZXNzIHRoZSBjb250cmFzdCAnY2VsbHR5cGVUcmVnPTAnIHdpdGggb3VyIHN0YXRpc3RpY2FsIHRlc3QuCgpgYGB7cn0KTCA8LSBtYWtlQ29udHJhc3QoImNlbGx0eXBlVHJlZz0wIiwgcGFyYW1ldGVyTmFtZXMgPSBjKCJjZWxsdHlwZVRyZWciKSkKcGUgPC0gaHlwb3RoZXNpc1Rlc3Qob2JqZWN0ID0gcGUsIGkgPSAicHJvdGVpblJvYnVzdCIsIGNvbnRyYXN0ID0gTCkKYGBgCgojIyBQbG90cwoKIyMjIFZvbGNhbm8tcGxvdAoKCmBgYHtyLHdhcm5pbmc9RkFMU0V9CnZvbGNhbm8gPC0gZ2dwbG90KHJvd0RhdGEocGVbWyJwcm90ZWluUm9idXN0Il1dKSRjZWxsdHlwZVRyZWcsCiAgICAgICAgICAgICAgICAgYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChwdmFsKSwgY29sb3IgPSBhZGpQdmFsIDwgMC4wNSkpICsKIGdlb21fcG9pbnQoY2V4ID0gMi41KSArCiBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYWxwaGEoYygiYmxhY2siLCAicmVkIiksIDAuNSkpICsgdGhlbWVfbWluaW1hbCgpCnZvbGNhbm8KYGBgCgoKIyMjIEhlYXRtYXAKCldlIGZpcnN0IHNlbGVjdCB0aGUgbmFtZXMgb2YgdGhlIHByb3RlaW5zIHRoYXQgd2VyZSBkZWNsYXJlZCBzaWduZmljYW50LgoKYGBge3J9CnNpZ05hbWVzIDwtIHJvd0RhdGEocGVbWyJwcm90ZWluUm9idXN0Il1dKSRjZWxsdHlwZVRyZWcgJT4lCiByb3duYW1lc190b19jb2x1bW4oInByb3RlaW5Sb2J1c3QiKSAlPiUKIGZpbHRlcihhZGpQdmFsPDAuMDUpICU+JQogcHVsbChwcm90ZWluUm9idXN0KQpoZWF0bWFwKGFzc2F5KHBlW1sicHJvdGVpblJvYnVzdCJdXSlbc2lnTmFtZXMsIF0pCmBgYAoKVGhlcmUgYXJlIGByIGxlbmd0aChzaWdOYW1lcylgIHByb3RlaW5zIHNpZ25pZmljYW50bHkgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGF0IHRoZSA1JSBGRFIgbGV2ZWwuCgpgYGB7cn0Kcm93RGF0YShwZVtbInByb3RlaW5Sb2J1c3QiXV0pJGNlbGx0eXBlVHJlZyAlPiUKICBmaWx0ZXIoYWRqUHZhbDwwLjA1KQpgYGAKCgoKIyMjIERldGFpbCBwbG90cwoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmZvciAocHJvdE5hbWUgaW4gc2lnTmFtZXNbMTo1XSkKewpwZVBsb3QgPC0gcGVbcHJvdE5hbWUsICwgYygicGVwdGlkZU5vcm0iLCJwcm90ZWluUm9idXN0IildCnBlUGxvdERmIDwtIGRhdGEuZnJhbWUobG9uZ0Zvcm1hdChwZVBsb3QpKQpwZVBsb3REZiRhc3NheSA8LSBmYWN0b3IocGVQbG90RGYkYXNzYXksCiAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygicGVwdGlkZU5vcm0iLCAicHJvdGVpblJvYnVzdCIpKQpwZVBsb3REZiRjZWxsdHlwZSA8LSBhcy5mYWN0b3IoY29sRGF0YShwZVBsb3QpW3BlUGxvdERmJGNvbG5hbWUsICJjZWxsdHlwZSJdKQoKIyBwbG90dGluZwpwMSA8LSBnZ3Bsb3QoZGF0YSA9IHBlUGxvdERmLAogICAgICBhZXMoeCA9IGNvbG5hbWUsIHkgPSB2YWx1ZSwgZ3JvdXAgPSByb3duYW1lKSkgKwogICBnZW9tX2xpbmUoKSArIGdlb21fcG9pbnQoKSArICB0aGVtZV9taW5pbWFsKCkgKwogICBmYWNldF9ncmlkKH5hc3NheSkgKyBnZ3RpdGxlKHByb3ROYW1lKQpwcmludChwMSkKCiMgcGxvdHRpbmcgMgpwMiA8LSBnZ3Bsb3QocGVQbG90RGYsIGFlcyh4ID0gY29sbmFtZSwgeSA9IHZhbHVlLCBmaWxsID0gY2VsbHR5cGUpKSArCiBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZSA9IE5BKSArIGdlb21fcG9pbnQocG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAuMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKHNoYXBlID0gcm93bmFtZSkpICsKIHNjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXMgPSAxOm5yb3cocGVQbG90RGYpKSArCiBsYWJzKHRpdGxlID0gcHJvdE5hbWUsIHggPSAic2FtcGxlIiwgeSA9ICJwZXB0aWRlIGludGVuc2l0eSAobG9nMikiKSArIHRoZW1lX21pbmltYWwoKQogZmFjZXRfZ3JpZCh+YXNzYXkpCnByaW50KHAyKQp9CmBgYAo=