1 Background

This case-study is a subset of the data of the 6th study of the Clinical Proteomic Technology Assessment for Cancer (CPTAC). In this experiment, the authors spiked the Sigma Universal Protein Standard mixture 1 (UPS1) containing 48 different human proteins in a protein background of 60 ng/\(\mu\)L Saccharomyces cerevisiae strain BY4741. Two different spike-in concentrations were used: 6A (0.25 fmol UPS1 proteins/\(\mu\)L) and 6B (0.74 fmol UPS1 proteins/\(\mu\)L) [5]. We limited ourselves to the data of LTQ-Orbitrap W at site 56. The data were searched with MaxQuant version 1.5.2.8, and detailed search settings were described in Goeminne et al. (2016) [1]. Three replicates are available for each concentration.

2 Data

We first import the data from peptideRaws.txt file. This is the file containing your peptideRaw-level intensities. For a MaxQuant search [6], this peptideRaws.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 the raw files were saved. In this vignette, we use a MaxQuant peptideRaws file which is a subset of the cptac study. This data is available in the msdata package. 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)

proteinFile <- "https://raw.githubusercontent.com/statOmics/SGA2020/data/quantification/cptacAvsB_lab3/proteinGroups.txt"

ecols <- MSnbase::grepEcols(
  peptidesFile,
  "Intensity ",
  split = "\t")

pe <- readQFeatures(
  table = proteinFile,
  fnames = 1,
  ecol = ecols,
  name = "proteinMaxLFQ", sep="\t")

In the following code chunk, we can extract the spikein condition from the raw file name.

cond <- which(
  strsplit(colnames(pe)[[1]][1], split = "")[[1]] == "A") # find where condition is stored

colData(pe)$condition <- substr(colnames(pe), cond, cond) %>%
  unlist %>%  
  as.factor

3 Preprocessing

This section preforms standard preprocessing for the peptide data. This include log transformation, filtering and summarisation of the data.

3.1 Log transform the data

pe <- zeroIsNA(pe, "proteinMaxLFQ") # convert 0 to NA
pe <- logTransform(pe, base = 2, i = "proteinMaxLFQ", name = "proteinLog")
limma::plotDensities(assay(pe[["proteinLog"]]))

3.2 Quantile normalize the data

pe <- normalize(pe, i = "proteinLog", method = "quantiles", name = "proteinMaxLFQNorm")

3.3 Explore quantile normalized data

After quantile normalisation the density curves for all samples coincide.

limma::plotDensities(assay(pe[["proteinMaxLFQNorm"]]))

This is more clearly seen is a boxplot.

boxplot(assay(pe[["proteinMaxLFQNorm"]]), col = palette()[-1],
        main = "Protein distribtutions upon 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[["proteinMaxLFQNorm"]]), col = as.numeric(colData(pe)$condition))

The first axis in the plot is showing the leading log fold changes (differences on the log scale) between the samples. We notice that the leading differences (log FC) in the normalized and log transformed proteinMax data seems to be driven by technical variability. But, in the second dimension we see a separation according to the spike-in condition

4 Data Analysis

4.1 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 = "proteinMaxLFQNorm", formula = ~condition)

4.2 Inference

First, we extract the parameter names of the model.

getCoef(rowData(pe[["proteinMaxLFQNorm"]])$msqrobModels[[1]])
## [1] NA

Spike-in condition a is the reference class. So the mean log2 expression for samples from condition a is ‘(Intercept). The mean log2 expression for samples from condition B is’(Intercept)+conditionB’. Hence, the average log2 fold change between condition b and condition a is modelled using the parameter ‘conditionB’. Thus, we assess the contrast ‘conditionB=0’ with our statistical test.

L <- makeContrast("conditionB=0", parameterNames = c("conditionB"))
pe <- hypothesisTest(object = pe, i = "proteinMaxLFQNorm", contrast = L)

4.3 Plots

4.3.1 Volcano-plot

volcano <- ggplot(rowData(pe[["proteinMaxLFQNorm"]])$conditionB,
                  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

4.3.2 Heatmap

We first select the names of the proteins that were declared signficant.

sigNames <- rowData(pe[["proteinMaxLFQNorm"]])$conditionB %>%
  rownames_to_column("proteinMaxLFQNorm") %>%
  filter(adjPval<0.05) %>%
  pull(proteinMaxLFQNorm)
heatmap(assay(pe[["proteinMaxLFQNorm"]])[sigNames, ])

4.3.3 Boxplots

We make boxplot of the log2 FC and stratify according to the whether a protein is spiked or not.

rowData(pe[["proteinMaxLFQNorm"]])$conditionB %>%
  rownames_to_column(var = "protein") %>%
  ggplot(aes(x=grepl("UPS",protein),y=logFC)) +
  geom_boxplot() +
  xlab("UPS") +
  geom_segment(
    x = 1.5,
    xend = 2.5,
    y = log2(0.74/0.25),
    yend = log2(0.74/0.25),
    colour="red") +
  geom_segment(
    x = 0.5,
    xend = 1.5,
    y = 0,
    yend = 0,
    colour="red") +
  annotate(
    "text",
    x = c(1,2),
    y = c(0,log2(0.74/0.25))+.1,
    label = c(
      "log2 FC Ecoli = 0",
      paste0("log2 FC UPS = ",round(log2(0.74/0.25),2))
      ),
    colour = "red")
## Warning: Removed 777 rows containing non-finite values (stat_boxplot).

4.3.4 Detail plots

for (protName in sigNames)
{
pePlot <- pe[protName, , c("proteinMaxLFQNorm")]
pePlotDf <- data.frame(longFormat(pePlot))
pePlotDf$assay <- factor(pePlotDf$assay,
                        levels = c("proteinMaxLFQNorm"))
pePlotDf$condition <- as.factor(colData(pePlot)[pePlotDf$colname, "condition"])

# 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 = condition)) +
  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)
}

LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIHByb3Rlb21pY3MgZGF0YSBhbmFseXNpczogTWF4TEZRIFN1bW1hcml6YXRpb24iCmF1dGhvcjogIkxpZXZlbiBDbGVtZW50IgpkYXRlOiAic3RhdE9taWNzLCBHaGVudCBVbml2ZXJzaXR5IChodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8pIgpvdXRwdXQ6CiAgICBodG1sX2RvY3VtZW50OgogICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICAgIHRoZW1lOiBjb3NtbwogICAgICB0b2M6IHRydWUKICAgICAgdG9jX2Zsb2F0OiB0cnVlCiAgICAgIGhpZ2hsaWdodDogdGFuZ28KICAgICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCi0tLQoKCiMgQmFja2dyb3VuZApUaGlzIGNhc2Utc3R1ZHkgaXMgYSBzdWJzZXQgb2YgdGhlIGRhdGEgb2YgdGhlIDZ0aCBzdHVkeSBvZiB0aGUgQ2xpbmljYWwKUHJvdGVvbWljIFRlY2hub2xvZ3kgQXNzZXNzbWVudCBmb3IgQ2FuY2VyIChDUFRBQykuCkluIHRoaXMgZXhwZXJpbWVudCwgdGhlIGF1dGhvcnMgc3Bpa2VkIHRoZSBTaWdtYSBVbml2ZXJzYWwgUHJvdGVpbiBTdGFuZGFyZAptaXh0dXJlIDEgKFVQUzEpIGNvbnRhaW5pbmcgNDggZGlmZmVyZW50IGh1bWFuIHByb3RlaW5zIGluIGEgcHJvdGVpbiBiYWNrZ3JvdW5kCm9mIDYwIG5nLyRcbXUkTCBTYWNjaGFyb215Y2VzIGNlcmV2aXNpYWUgc3RyYWluIEJZNDc0MS4KVHdvIGRpZmZlcmVudCBzcGlrZS1pbiBjb25jZW50cmF0aW9ucyB3ZXJlIHVzZWQ6CjZBICgwLjI1IGZtb2wgVVBTMSBwcm90ZWlucy8kXG11JEwpIGFuZCA2QiAoMC43NCBmbW9sIFVQUzEgcHJvdGVpbnMvJFxtdSRMKSBbNV0uCldlIGxpbWl0ZWQgb3Vyc2VsdmVzIHRvIHRoZSBkYXRhIG9mIExUUS1PcmJpdHJhcCBXIGF0IHNpdGUgNTYuClRoZSBkYXRhIHdlcmUgc2VhcmNoZWQgd2l0aCBNYXhRdWFudCB2ZXJzaW9uIDEuNS4yLjgsIGFuZApkZXRhaWxlZCBzZWFyY2ggc2V0dGluZ3Mgd2VyZSBkZXNjcmliZWQgaW4gR29lbWlubmUgZXQgYWwuICgyMDE2KSBbMV0uClRocmVlIHJlcGxpY2F0ZXMgYXJlIGF2YWlsYWJsZSBmb3IgZWFjaCBjb25jZW50cmF0aW9uLgoKIyBEYXRhCgpXZSBmaXJzdCBpbXBvcnQgdGhlIGRhdGEgZnJvbSBwZXB0aWRlUmF3cy50eHQgZmlsZS4gVGhpcyBpcyB0aGUgZmlsZSBjb250YWluaW5nCnlvdXIgcGVwdGlkZVJhdy1sZXZlbCBpbnRlbnNpdGllcy4gRm9yIGEgTWF4UXVhbnQgc2VhcmNoIFs2XSwKdGhpcyBwZXB0aWRlUmF3cy50eHQgZmlsZSBjYW4gYmUgZm91bmQgYnkgZGVmYXVsdCBpbiB0aGUKInBhdGhfdG9fcmF3X2ZpbGVzL2NvbWJpbmVkL3R4dC8iIGZvbGRlciBmcm9tIHRoZSBNYXhRdWFudCBvdXRwdXQsCndpdGggInBhdGhfdG9fcmF3X2ZpbGVzIiB0aGUgZm9sZGVyIHdoZXJlIHRoZSByYXcgZmlsZXMgd2VyZSBzYXZlZC4KSW4gdGhpcyB2aWduZXR0ZSwgd2UgdXNlIGEgTWF4UXVhbnQgcGVwdGlkZVJhd3MgZmlsZSB3aGljaCBpcyBhIHN1YnNldApvZiB0aGUgY3B0YWMgc3R1ZHkuIFRoaXMgZGF0YSBpcyBhdmFpbGFibGUgaW4gdGhlIGBtc2RhdGFgIHBhY2thZ2UuClRvIGltcG9ydCB0aGUgZGF0YSB3ZSB1c2UgdGhlIGBRRmVhdHVyZXNgIHBhY2thZ2UuCgpXZSBnZW5lcmF0ZSB0aGUgb2JqZWN0IHBlcHRpZGVSYXdGaWxlIHdpdGggdGhlIHBhdGggdG8gdGhlIHBlcHRpZGVSYXdzLnR4dCBmaWxlLgpVc2luZyB0aGUgYGdyZXBFY29sc2AgZnVuY3Rpb24sIHdlIGZpbmQgdGhlIGNvbHVtbnMgdGhhdCBjb250YWluIHRoZSBleHByZXNzaW9uCmRhdGEgb2YgdGhlIHBlcHRpZGVSYXdzIGluIHRoZSBwZXB0aWRlUmF3cy50eHQgZmlsZS4KCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShsaW1tYSkKbGlicmFyeShRRmVhdHVyZXMpCmxpYnJhcnkobXNxcm9iMikKbGlicmFyeShwbG90bHkpCgpwcm90ZWluRmlsZSA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3N0YXRPbWljcy9TR0EyMDIwL2RhdGEvcXVhbnRpZmljYXRpb24vY3B0YWNBdnNCX2xhYjMvcHJvdGVpbkdyb3Vwcy50eHQiCgplY29scyA8LSBNU25iYXNlOjpncmVwRWNvbHMoCiAgcGVwdGlkZXNGaWxlLAogICJJbnRlbnNpdHkgIiwKICBzcGxpdCA9ICJcdCIpCgpwZSA8LSByZWFkUUZlYXR1cmVzKAogIHRhYmxlID0gcHJvdGVpbkZpbGUsCiAgZm5hbWVzID0gMSwKICBlY29sID0gZWNvbHMsCiAgbmFtZSA9ICJwcm90ZWluTWF4TEZRIiwgc2VwPSJcdCIpCmBgYAoKSW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rLCB3ZSBjYW4gZXh0cmFjdCB0aGUgc3Bpa2VpbiBjb25kaXRpb24gZnJvbSB0aGUgcmF3IGZpbGUgbmFtZS4KCmBgYHtyfQpjb25kIDwtIHdoaWNoKAogIHN0cnNwbGl0KGNvbG5hbWVzKHBlKVtbMV1dWzFdLCBzcGxpdCA9ICIiKVtbMV1dID09ICJBIikgIyBmaW5kIHdoZXJlIGNvbmRpdGlvbiBpcyBzdG9yZWQKCmNvbERhdGEocGUpJGNvbmRpdGlvbiA8LSBzdWJzdHIoY29sbmFtZXMocGUpLCBjb25kLCBjb25kKSAlPiUKICB1bmxpc3QgJT4lICAKICBhcy5mYWN0b3IKYGBgCgoKIyBQcmVwcm9jZXNzaW5nCgpUaGlzIHNlY3Rpb24gcHJlZm9ybXMgc3RhbmRhcmQgcHJlcHJvY2Vzc2luZyBmb3IgdGhlIHBlcHRpZGUgZGF0YS4gVGhpcwppbmNsdWRlIGxvZyB0cmFuc2Zvcm1hdGlvbiwgZmlsdGVyaW5nIGFuZCBzdW1tYXJpc2F0aW9uIG9mIHRoZSBkYXRhLgoKIyMgTG9nIHRyYW5zZm9ybSB0aGUgZGF0YQoKYGBge3J9CnBlIDwtIHplcm9Jc05BKHBlLCAicHJvdGVpbk1heExGUSIpICMgY29udmVydCAwIHRvIE5BCnBlIDwtIGxvZ1RyYW5zZm9ybShwZSwgYmFzZSA9IDIsIGkgPSAicHJvdGVpbk1heExGUSIsIG5hbWUgPSAicHJvdGVpbkxvZyIpCmxpbW1hOjpwbG90RGVuc2l0aWVzKGFzc2F5KHBlW1sicHJvdGVpbkxvZyJdXSkpCmBgYAoKIyMgUXVhbnRpbGUgbm9ybWFsaXplIHRoZSBkYXRhCmBgYHtyfQpwZSA8LSBub3JtYWxpemUocGUsIGkgPSAicHJvdGVpbkxvZyIsIG1ldGhvZCA9ICJxdWFudGlsZXMiLCBuYW1lID0gInByb3RlaW5NYXhMRlFOb3JtIikKYGBgCgoKIyMgRXhwbG9yZSBxdWFudGlsZSBub3JtYWxpemVkIGRhdGEKCkFmdGVyIHF1YW50aWxlIG5vcm1hbGlzYXRpb24gdGhlIGRlbnNpdHkgY3VydmVzIGZvciBhbGwgc2FtcGxlcyBjb2luY2lkZS4KCmBgYHtyfQpsaW1tYTo6cGxvdERlbnNpdGllcyhhc3NheShwZVtbInByb3RlaW5NYXhMRlFOb3JtIl1dKSkKYGBgCgpUaGlzIGlzIG1vcmUgY2xlYXJseSBzZWVuIGlzIGEgYm94cGxvdC4KCmBgYHtyLH0KYm94cGxvdChhc3NheShwZVtbInByb3RlaW5NYXhMRlFOb3JtIl1dKSwgY29sID0gcGFsZXR0ZSgpWy0xXSwKICAgICAgICBtYWluID0gIlByb3RlaW4gZGlzdHJpYnR1dGlvbnMgdXBvbiBub3JtYWxpc2F0aW9uIiwgeWxhYiA9ICJpbnRlbnNpdHkiKQoKYGBgCgoKV2UgY2FuIHZpc3VhbGl6ZSBvdXIgZGF0YSB1c2luZyBhIE11bHRpIERpbWVuc2lvbmFsIFNjYWxpbmcgcGxvdCwKZWcuIGFzIHByb3ZpZGVkIGJ5IHRoZSBgbGltbWFgIHBhY2thZ2UuCgpgYGB7cn0KbGltbWE6OnBsb3RNRFMoYXNzYXkocGVbWyJwcm90ZWluTWF4TEZRTm9ybSJdXSksIGNvbCA9IGFzLm51bWVyaWMoY29sRGF0YShwZSkkY29uZGl0aW9uKSkKYGBgCgpUaGUgZmlyc3QgYXhpcyBpbiB0aGUgcGxvdCBpcyBzaG93aW5nIHRoZSBsZWFkaW5nIGxvZyBmb2xkIGNoYW5nZXMKKGRpZmZlcmVuY2VzIG9uIHRoZSBsb2cgc2NhbGUpIGJldHdlZW4gdGhlIHNhbXBsZXMuCldlIG5vdGljZSB0aGF0IHRoZSBsZWFkaW5nIGRpZmZlcmVuY2VzIChsb2cgRkMpCmluIHRoZSBub3JtYWxpemVkIGFuZCBsb2cgdHJhbnNmb3JtZWQgcHJvdGVpbk1heCBkYXRhIHNlZW1zIHRvIGJlIGRyaXZlbiBieSB0ZWNobmljYWwgdmFyaWFiaWxpdHkuCkJ1dCwgaW4gdGhlIHNlY29uZCBkaW1lbnNpb24gd2Ugc2VlIGEgc2VwYXJhdGlvbiBhY2NvcmRpbmcgdG8gdGhlIHNwaWtlLWluIGNvbmRpdGlvbgoKIyBEYXRhIEFuYWx5c2lzCgojIyBFc3RpbWF0aW9uCgpXZSBtb2RlbCB0aGUgcHJvdGVpbiBsZXZlbCBleHByZXNzaW9uIHZhbHVlcyB1c2luZyBgbXNxcm9iYC4KQnkgZGVmYXVsdCBgbXNxcm9iMmAgZXN0aW1hdGVzIHRoZSBtb2RlbCBwYXJhbWV0ZXJzIHVzaW5nIHJvYnVzdCByZWdyZXNzaW9uLiAgCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KcGUgPC0gbXNxcm9iKG9iamVjdCA9IHBlLCBpID0gInByb3RlaW5NYXhMRlFOb3JtIiwgZm9ybXVsYSA9IH5jb25kaXRpb24pCmBgYAoKIyMgSW5mZXJlbmNlCgpGaXJzdCwgd2UgZXh0cmFjdCB0aGUgcGFyYW1ldGVyIG5hbWVzIG9mIHRoZSBtb2RlbC4KYGBge3J9CmdldENvZWYocm93RGF0YShwZVtbInByb3RlaW5NYXhMRlFOb3JtIl1dKSRtc3Fyb2JNb2RlbHNbWzFdXSkKYGBgCgpTcGlrZS1pbiBjb25kaXRpb24gYSBpcyB0aGUgcmVmZXJlbmNlIGNsYXNzLiBTbyB0aGUgbWVhbiBsb2cyIGV4cHJlc3Npb24KZm9yIHNhbXBsZXMgZnJvbSBjb25kaXRpb24gYSBpcyAnKEludGVyY2VwdCkuClRoZSBtZWFuIGxvZzIgZXhwcmVzc2lvbiBmb3Igc2FtcGxlcyBmcm9tIGNvbmRpdGlvbiBCIGlzICcoSW50ZXJjZXB0KStjb25kaXRpb25CJy4KSGVuY2UsIHRoZSBhdmVyYWdlIGxvZzIgZm9sZCBjaGFuZ2UgYmV0d2VlbiBjb25kaXRpb24gYiBhbmQKY29uZGl0aW9uIGEgaXMgbW9kZWxsZWQgdXNpbmcgdGhlIHBhcmFtZXRlciAnY29uZGl0aW9uQicuClRodXMsIHdlIGFzc2VzcyB0aGUgY29udHJhc3QgJ2NvbmRpdGlvbkI9MCcgd2l0aCBvdXIgc3RhdGlzdGljYWwgdGVzdC4KCmBgYHtyfQpMIDwtIG1ha2VDb250cmFzdCgiY29uZGl0aW9uQj0wIiwgcGFyYW1ldGVyTmFtZXMgPSBjKCJjb25kaXRpb25CIikpCnBlIDwtIGh5cG90aGVzaXNUZXN0KG9iamVjdCA9IHBlLCBpID0gInByb3RlaW5NYXhMRlFOb3JtIiwgY29udHJhc3QgPSBMKQpgYGAKCgojIyBQbG90cwoKIyMjIFZvbGNhbm8tcGxvdAoKCmBgYHtyLHdhcm5pbmc9RkFMU0V9CnZvbGNhbm8gPC0gZ2dwbG90KHJvd0RhdGEocGVbWyJwcm90ZWluTWF4TEZRTm9ybSJdXSkkY29uZGl0aW9uQiwKICAgICAgICAgICAgICAgICAgYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChwdmFsKSwgY29sb3IgPSBhZGpQdmFsIDwgMC4wNSkpICsKICBnZW9tX3BvaW50KGNleCA9IDIuNSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBhbHBoYShjKCJibGFjayIsICJyZWQiKSwgMC41KSkgKyB0aGVtZV9taW5pbWFsKCkKdm9sY2FubwpgYGAKCiMjIyBIZWF0bWFwCgpXZSBmaXJzdCBzZWxlY3QgdGhlIG5hbWVzIG9mIHRoZSBwcm90ZWlucyB0aGF0IHdlcmUgZGVjbGFyZWQgc2lnbmZpY2FudC4KCmBgYHtyfQpzaWdOYW1lcyA8LSByb3dEYXRhKHBlW1sicHJvdGVpbk1heExGUU5vcm0iXV0pJGNvbmRpdGlvbkIgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJwcm90ZWluTWF4TEZRTm9ybSIpICU+JQogIGZpbHRlcihhZGpQdmFsPDAuMDUpICU+JQogIHB1bGwocHJvdGVpbk1heExGUU5vcm0pCmhlYXRtYXAoYXNzYXkocGVbWyJwcm90ZWluTWF4TEZRTm9ybSJdXSlbc2lnTmFtZXMsIF0pCmBgYAoKIyMjIEJveHBsb3RzCgpXZSBtYWtlIGJveHBsb3Qgb2YgdGhlIGxvZzIgRkMgYW5kIHN0cmF0aWZ5IGFjY29yZGluZyB0byB0aGUgd2hldGhlciBhIHByb3RlaW4gaXMgc3Bpa2VkIG9yIG5vdC4KCmBgYHtyfQpyb3dEYXRhKHBlW1sicHJvdGVpbk1heExGUU5vcm0iXV0pJGNvbmRpdGlvbkIgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJwcm90ZWluIikgJT4lCiAgZ2dwbG90KGFlcyh4PWdyZXBsKCJVUFMiLHByb3RlaW4pLHk9bG9nRkMpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHhsYWIoIlVQUyIpICsKICBnZW9tX3NlZ21lbnQoCiAgICB4ID0gMS41LAogICAgeGVuZCA9IDIuNSwKICAgIHkgPSBsb2cyKDAuNzQvMC4yNSksCiAgICB5ZW5kID0gbG9nMigwLjc0LzAuMjUpLAogICAgY29sb3VyPSJyZWQiKSArCiAgZ2VvbV9zZWdtZW50KAogICAgeCA9IDAuNSwKICAgIHhlbmQgPSAxLjUsCiAgICB5ID0gMCwKICAgIHllbmQgPSAwLAogICAgY29sb3VyPSJyZWQiKSArCiAgYW5ub3RhdGUoCiAgICAidGV4dCIsCiAgICB4ID0gYygxLDIpLAogICAgeSA9IGMoMCxsb2cyKDAuNzQvMC4yNSkpKy4xLAogICAgbGFiZWwgPSBjKAogICAgICAibG9nMiBGQyBFY29saSA9IDAiLAogICAgICBwYXN0ZTAoImxvZzIgRkMgVVBTID0gIixyb3VuZChsb2cyKDAuNzQvMC4yNSksMikpCiAgICAgICksCiAgICBjb2xvdXIgPSAicmVkIikKYGBgCgojIyMgRGV0YWlsIHBsb3RzCgoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmZvciAocHJvdE5hbWUgaW4gc2lnTmFtZXMpCnsKcGVQbG90IDwtIHBlW3Byb3ROYW1lLCAsIGMoInByb3RlaW5NYXhMRlFOb3JtIildCnBlUGxvdERmIDwtIGRhdGEuZnJhbWUobG9uZ0Zvcm1hdChwZVBsb3QpKQpwZVBsb3REZiRhc3NheSA8LSBmYWN0b3IocGVQbG90RGYkYXNzYXksCiAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoInByb3RlaW5NYXhMRlFOb3JtIikpCnBlUGxvdERmJGNvbmRpdGlvbiA8LSBhcy5mYWN0b3IoY29sRGF0YShwZVBsb3QpW3BlUGxvdERmJGNvbG5hbWUsICJjb25kaXRpb24iXSkKCiMgcGxvdHRpbmcKcDEgPC0gZ2dwbG90KGRhdGEgPSBwZVBsb3REZiwKICAgICAgIGFlcyh4ID0gY29sbmFtZSwgeSA9IHZhbHVlLCBncm91cCA9IHJvd25hbWUpKSArCiAgICBnZW9tX2xpbmUoKSArIGdlb21fcG9pbnQoKSArICB0aGVtZV9taW5pbWFsKCkgKwogICAgZmFjZXRfZ3JpZCh+YXNzYXkpICsgZ2d0aXRsZShwcm90TmFtZSkKcHJpbnQocDEpCgojIHBsb3R0aW5nIDIKcDIgPC0gZ2dwbG90KHBlUGxvdERmLCBhZXMoeCA9IGNvbG5hbWUsIHkgPSB2YWx1ZSwgZmlsbCA9IGNvbmRpdGlvbikpICsKICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZSA9IE5BKSArIGdlb21fcG9pbnQocG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAuMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyhzaGFwZSA9IHJvd25hbWUpKSArCiAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcyA9IDE6bnJvdyhwZVBsb3REZikpICsKICBsYWJzKHRpdGxlID0gcHJvdE5hbWUsIHggPSAic2FtcGxlIiwgeSA9ICJwZXB0aWRlIGludGVuc2l0eSAobG9nMikiKSArIHRoZW1lX21pbmltYWwoKQogIGZhY2V0X2dyaWQofmFzc2F5KQpwcmludChwMikKfQpgYGAK