In our analysis of the SLE WB compendium, we were able to do evaluations structured around cell types because we had neutrophil counts in one of the datasets under consideration. For NARES, we do not have any information about the cell type composition of the samples (e.g., histological data, which can be semi-quantitative in nature).

MCPcounter (Becht, et al. Genome Biology. 2016.) is a method for estimating cell type abundance in solid tissues. The original paper also explicitly tested the approach in non-cancerous tissues. (Many methods for immune infiltrate estimation are developed in the context of the tumor microenvironment. Note that in the PLIER preprint, PLIER compared favorably to another method, CIBERSORT (Newman, et al. Nature Methods. 2015.), when using CyTOF measurements as a gold standard.)

In our analysis of the NARES PLIER model, we noted that the neutrophil and ECM signals appeared to be among the strongest in the NARES gene expression data. In this notebook, we’re interested if PLIER neutrophil-associated LVs, both from the NARES and recount2 models, are well-correlated with the neutrophil estimates from MCPcounter.

PLIER has appealing features over MCPcounter: it is explicitly designed to capture biological signal outside cell types (e.g., canonical pathways) and, because it doesn’t only learn geneset-associated LVs, can model technical variance. If we get similar estimates with the two methods (particularly with the multi-PLIER approach), it supports the notion that a single method/model can be useful for a broad set of biological questions & contexts.

Install MCPcounter

This is not currently in the Docker image we’re using for this project.

devtools::install_github("ebecht/MCPcounter", 
                         ref = "a79614eee002c88c64725d69140c7653e7c379b4",
                         subdir = "Source",
                         dependencies = TRUE)
Downloading GitHub repo ebecht/MCPcounter@a79614eee002c88c64725d69140c7653e7c379b4
from URL https://api.github.com/repos/ebecht/MCPcounter/zipball/a79614eee002c88c64725d69140c7653e7c379b4
Installing MCPcounter
'/usr/local/lib/R/bin/R' --no-site-file --no-environ --no-save --no-restore  \
  --quiet CMD INSTALL  \
  '/tmp/RtmpodPVj8/devtoolsaac2090ee08/ebecht-MCPcounter-a79614e/Source'  \
  --library='/usr/local/lib/R/site-library' --install-tests 

Functions and directory setup

`%>%` <- dplyr::`%>%`
# plot and result directory setup for this notebook
plot.dir <- file.path("plots", "14")
dir.create(plot.dir, recursive = TRUE, showWarnings = FALSE)
results.dir <- file.path("results", "14")
dir.create(results.dir, recursive = TRUE, showWarnings = FALSE)

Read in data

NARES expression

# get NARES expression matrix
exprs.file <- file.path("data", "expression_data", 
                        "NARES_SCANfast_ComBat_with_GeneSymbol.pcl")
exprs.df <- data.table::fread(exprs.file, data.table = FALSE)
exprs.mat <- dplyr::select(exprs.df, -(EntrezID:GeneSymbol))
rownames(exprs.mat) <- exprs.df$GeneSymbol
rm(exprs.df)

NARES PLIER model

nares.plier.file <- file.path("results", "12", "NARES_PLIER_model.RDS")
nares.plier <- readRDS(nares.plier.file)
nares.b <- nares.plier$B

recount2 NARES B

recount.b.file <- file.path("results", "13", "NARES_recount2_B.RDS")
recount.b <- readRDS(file = recount.b.file)

Run MCPcounter

mcp.results <- 
  MCPcounter::MCPcounter.estimate(exprs.mat, featuresType = "HUGO_symbols")
mcp.melt <- reshape2::melt(mcp.results, varnames = c("Cell_type", "Sample"),
                           value.name = "MCP_estimate")
readr::write_tsv(mcp.melt, 
                 file.path(results.dir, 
                           "NARES_ComBat_MCPCounter_results_tidy.tsv"))

Compare to PLIER LVs

NARES LV3 was the neutrophil-associated LV in the NARES PLIER model; its best match is recount LV603 on the basis of the Z matrices (13-compare_NARES_B). recount LV603 also appears to be pretty neutrophil-specific (not significantly associated with other myeloid cell types; 07-sle_cell_type_recount2_model). These are the LVs we’ll compare to the MCPcounter estimates.

Data wrangling

# tidy neutrophil-associated LVs
neutro.lv.df <- as.data.frame(nares.b["3,IRIS_Neutrophil-Resting", ])
neutro.lv.df <- tibble::rownames_to_column(neutro.lv.df)
colnames(neutro.lv.df) <- c("Sample", "NARES_LV3")
recount.lv.df <- as.data.frame(recount.b["603,SVM Neutrophils", ])
recount.lv.df <- tibble::rownames_to_column(recount.lv.df)
colnames(recount.lv.df) <- c("Sample", "recount_LV603")
neutro.lv.df <- dplyr::inner_join(neutro.lv.df, recount.lv.df, 
                                  by = "Sample")
head(neutro.lv.df)
# join with MCPcounter neutrophil estimates
neutro.df <- dplyr::filter(mcp.melt, Cell_type == "Neutrophils") %>%
                dplyr::inner_join(y = neutro.lv.df, by = "Sample")
Column `Sample` joining factor and character vector, coercing into character vector
neutro.file <- file.path(results.dir, "NARES_neutrophil_LV_mcp_all.tsv")
readr::write_tsv(neutro.df, path = neutro.file)

Plotting

summary(lm(neutro.df$MCP_estimate ~ neutro.df$NARES_LV3))

Call:
lm(formula = neutro.df$MCP_estimate ~ neutro.df$NARES_LV3)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.11900 -0.02890 -0.00306  0.03302  0.13345 

Coefficients:
                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)         0.903203   0.005758  156.87   <2e-16 ***
neutro.df$NARES_LV3 0.307055   0.004794   64.05   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.0502 on 74 degrees of freedom
Multiple R-squared:  0.9823,    Adjusted R-squared:  0.982 
F-statistic:  4103 on 1 and 74 DF,  p-value: < 2.2e-16
nares.p <- neutro.df %>%
  ggplot2::ggplot(ggplot2::aes(x = NARES_LV3, y = MCP_estimate)) +
  ggplot2::geom_point(alpha = 0.7) +
  ggplot2::geom_smooth(method = "lm") +
  ggplot2::theme_bw() +
  ggplot2::labs(title = "NARES PLIER model",
                y = "MCPcounter Neutrophil Estimate",
                x = "NARES LV3") +
  ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, face = "bold"),
                 text = ggplot2::element_text(size = 15)) +
  ggplot2::annotate(geom = "text", x = -0.1, y = 2.25, 
                    label = "r-squared = 0.98", size = 5)
nares.p

summary(lm(neutro.df$MCP_estimate ~ neutro.df$recount_LV603))

Call:
lm(formula = neutro.df$MCP_estimate ~ neutro.df$recount_LV603)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.26979 -0.08089  0.00521  0.07853  0.32115 

Coefficients:
                        Estimate Std. Error t value Pr(>|t|)    
(Intercept)              0.90320    0.01348   66.98   <2e-16 ***
neutro.df$recount_LV603  1.57195    0.05995   26.22   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.1176 on 74 degrees of freedom
Multiple R-squared:  0.9028,    Adjusted R-squared:  0.9015 
F-statistic: 687.5 on 1 and 74 DF,  p-value: < 2.2e-16
recount.p <- neutro.df %>%
  ggplot2::ggplot(ggplot2::aes(x = recount_LV603, y = MCP_estimate)) +
  ggplot2::geom_point(alpha = 0.7) +
  ggplot2::geom_smooth(method = "lm") +
  ggplot2::theme_bw() +
  ggplot2::labs(title = "recount2 PLIER model",
                y = "MCPcounter Neutrophil Estimate",
                x = "recount LV603") +
  ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, face = "bold"),
                 text = ggplot2::element_text(size = 15)) +
  ggplot2::annotate(geom = "text", x = -0.1, y = 2.5, 
                    label = "r-squared = 0.90", size = 5)
recount.p

pdf(file.path(plot.dir, "NARES_MCPcounter_model_comparison.pdf"), width = 14,
    height = 7)
gridExtra::grid.arrange(nares.p, recount.p, ncol = 2)
dev.off()
null device 
          1 
LS0tCnRpdGxlOiAiTkFSRVMgTUNQY291bnRlciIKb3V0cHV0OiAgIAogIGh0bWxfbm90ZWJvb2s6IAogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKLS0tCgpJbiBvdXIgYW5hbHlzaXMgb2YgdGhlIFNMRSBXQiBjb21wZW5kaXVtLCB3ZSB3ZXJlIGFibGUgdG8gZG8gZXZhbHVhdGlvbnMgCnN0cnVjdHVyZWQgYXJvdW5kIGNlbGwgdHlwZXMgYmVjYXVzZSB3ZSBoYWQgbmV1dHJvcGhpbCBjb3VudHMgaW4gb25lIG9mIHRoZSAKZGF0YXNldHMgdW5kZXIgY29uc2lkZXJhdGlvbi4KRm9yIE5BUkVTLCB3ZSBkbyBub3QgaGF2ZSBhbnkgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGNlbGwgdHlwZSBjb21wb3NpdGlvbiBvZgp0aGUgc2FtcGxlcyAoZS5nLiwgaGlzdG9sb2dpY2FsIGRhdGEsIHdoaWNoIGNhbiBiZSBzZW1pLXF1YW50aXRhdGl2ZSBpbiBuYXR1cmUpLgoKW01DUGNvdW50ZXJdKGh0dHBzOi8vZ2l0aHViLmNvbS9lYmVjaHQvTUNQY291bnRlci90cmVlL2E3OTYxNGVlZTAwMmM4OGM2NDcyNWQ2OTE0MGM3NjUzZTdjMzc5YjQpIAooW0JlY2h0LCBldCBhbC4gX0dlbm9tZSBCaW9sb2d5Xy4gMjAxNi5dKGh0dHBzOi8vZG9pLm9yZy8xMC4xMTg2L3MxMzA1OS0wMTYtMTA3MC01KSkKaXMgYSBtZXRob2QgZm9yIGVzdGltYXRpbmcgY2VsbCB0eXBlIGFidW5kYW5jZSBpbiBzb2xpZCB0aXNzdWVzLgpUaGUgb3JpZ2luYWwgcGFwZXIgYWxzbyBleHBsaWNpdGx5IHRlc3RlZCB0aGUgYXBwcm9hY2ggaW4gbm9uLWNhbmNlcm91cyAKdGlzc3Vlcy4KKE1hbnkgbWV0aG9kcyBmb3IgaW1tdW5lIGluZmlsdHJhdGUgZXN0aW1hdGlvbiBhcmUgZGV2ZWxvcGVkIGluIHRoZSBjb250ZXh0Cm9mIHRoZSB0dW1vciBtaWNyb2Vudmlyb25tZW50LiBOb3RlIHRoYXQgaW4gdGhlIApbUExJRVIgcHJlcHJpbnRdKGh0dHBzOi8vZG9pLm9yZy8xMC4xMTAxLzExNjA2MSksIFBMSUVSIGNvbXBhcmVkIGZhdm9yYWJseSB0byAKYW5vdGhlciBtZXRob2QsIFtDSUJFUlNPUlRdKGh0dHBzOi8vY2liZXJzb3J0LnN0YW5mb3JkLmVkdSkgCihbTmV3bWFuLCBldCBhbC4gX05hdHVyZSBNZXRob2RzLl8gMjAxNS5dKGh0dHBzOi8vZG9pLm9yZzEwLjEwMzgvbm1ldGguMzMzNykpLCAKd2hlbiB1c2luZyBDeVRPRiBtZWFzdXJlbWVudHMgYXMgYSBnb2xkIHN0YW5kYXJkLikgCgpJbiBvdXIgYW5hbHlzaXMgb2YgdGhlIE5BUkVTIFBMSUVSIG1vZGVsLCB3ZSBub3RlZCB0aGF0IHRoZSBuZXV0cm9waGlsIGFuZApFQ00gc2lnbmFscyBhcHBlYXJlZCB0byBiZSBhbW9uZyB0aGUgc3Ryb25nZXN0IGluIHRoZSBOQVJFUyBnZW5lIGV4cHJlc3Npb24gCmRhdGEuIEluIHRoaXMgbm90ZWJvb2ssIHdlJ3JlIGludGVyZXN0ZWQgaWYgUExJRVIgbmV1dHJvcGhpbC1hc3NvY2lhdGVkIExWcywgCmJvdGggZnJvbSB0aGUgTkFSRVMgYW5kIHJlY291bnQyIG1vZGVscywgYXJlIHdlbGwtY29ycmVsYXRlZCB3aXRoIHRoZSBuZXV0cm9waGlsIAplc3RpbWF0ZXMgZnJvbSBNQ1Bjb3VudGVyLgoKUExJRVIgaGFzIGFwcGVhbGluZyBmZWF0dXJlcyBvdmVyIE1DUGNvdW50ZXI6IGl0IGlzIGV4cGxpY2l0bHkgZGVzaWduZWQgdG8gCmNhcHR1cmUgYmlvbG9naWNhbCBzaWduYWwgX291dHNpZGVfIGNlbGwgdHlwZXMgKGUuZy4sIGNhbm9uaWNhbCBwYXRod2F5cykgYW5kLCAKYmVjYXVzZSBpdCBkb2Vzbid0IG9ubHkgbGVhcm4gZ2VuZXNldC1hc3NvY2lhdGVkIExWcywgY2FuIG1vZGVsIHRlY2huaWNhbCAKdmFyaWFuY2UuIElmIHdlIGdldCBzaW1pbGFyIGVzdGltYXRlcyB3aXRoIHRoZSB0d28gbWV0aG9kcyAocGFydGljdWxhcmx5IHdpdGgKdGhlIG11bHRpLVBMSUVSIGFwcHJvYWNoKSwgaXQgc3VwcG9ydHMgdGhlIG5vdGlvbiB0aGF0IGEgc2luZ2xlIG1ldGhvZC9tb2RlbCBjYW4KYmUgdXNlZnVsIGZvciBhIGJyb2FkIHNldCBvZiBiaW9sb2dpY2FsIHF1ZXN0aW9ucyAmIGNvbnRleHRzLgoKIyMgSW5zdGFsbCBNQ1Bjb3VudGVyCgpUaGlzIGlzIG5vdCBjdXJyZW50bHkgaW4gdGhlIERvY2tlciBpbWFnZSB3ZSdyZSB1c2luZyBmb3IgdGhpcyBwcm9qZWN0LgoKYGBge3J9CmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiZWJlY2h0L01DUGNvdW50ZXIiLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHJlZiA9ICJhNzk2MTRlZWUwMDJjODhjNjQ3MjVkNjkxNDBjNzY1M2U3YzM3OWI0IiwKICAgICAgICAgICAgICAgICAgICAgICAgIHN1YmRpciA9ICJTb3VyY2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgZGVwZW5kZW5jaWVzID0gVFJVRSkKYGBgCgojIyBGdW5jdGlvbnMgYW5kIGRpcmVjdG9yeSBzZXR1cAoKYGBge3J9CmAlPiVgIDwtIGRwbHlyOjpgJT4lYApgYGAKCmBgYHtyfQojIHBsb3QgYW5kIHJlc3VsdCBkaXJlY3Rvcnkgc2V0dXAgZm9yIHRoaXMgbm90ZWJvb2sKcGxvdC5kaXIgPC0gZmlsZS5wYXRoKCJwbG90cyIsICIxNCIpCmRpci5jcmVhdGUocGxvdC5kaXIsIHJlY3Vyc2l2ZSA9IFRSVUUsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQpyZXN1bHRzLmRpciA8LSBmaWxlLnBhdGgoInJlc3VsdHMiLCAiMTQiKQpkaXIuY3JlYXRlKHJlc3VsdHMuZGlyLCByZWN1cnNpdmUgPSBUUlVFLCBzaG93V2FybmluZ3MgPSBGQUxTRSkKYGBgCgojIyBSZWFkIGluIGRhdGEKCiMjIyBOQVJFUyBleHByZXNzaW9uCgpgYGB7cn0KIyBnZXQgTkFSRVMgZXhwcmVzc2lvbiBtYXRyaXgKZXhwcnMuZmlsZSA8LSBmaWxlLnBhdGgoImRhdGEiLCAiZXhwcmVzc2lvbl9kYXRhIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJOQVJFU19TQ0FOZmFzdF9Db21CYXRfd2l0aF9HZW5lU3ltYm9sLnBjbCIpCmV4cHJzLmRmIDwtIGRhdGEudGFibGU6OmZyZWFkKGV4cHJzLmZpbGUsIGRhdGEudGFibGUgPSBGQUxTRSkKZXhwcnMubWF0IDwtIGRwbHlyOjpzZWxlY3QoZXhwcnMuZGYsIC0oRW50cmV6SUQ6R2VuZVN5bWJvbCkpCnJvd25hbWVzKGV4cHJzLm1hdCkgPC0gZXhwcnMuZGYkR2VuZVN5bWJvbApybShleHBycy5kZikKYGBgCgojIyMgTkFSRVMgUExJRVIgbW9kZWwKCmBgYHtyfQpuYXJlcy5wbGllci5maWxlIDwtIGZpbGUucGF0aCgicmVzdWx0cyIsICIxMiIsICJOQVJFU19QTElFUl9tb2RlbC5SRFMiKQpuYXJlcy5wbGllciA8LSByZWFkUkRTKG5hcmVzLnBsaWVyLmZpbGUpCm5hcmVzLmIgPC0gbmFyZXMucGxpZXIkQgpgYGAKCiMjIyByZWNvdW50MiBOQVJFUyBCCgpgYGB7cn0KcmVjb3VudC5iLmZpbGUgPC0gZmlsZS5wYXRoKCJyZXN1bHRzIiwgIjEzIiwgIk5BUkVTX3JlY291bnQyX0IuUkRTIikKcmVjb3VudC5iIDwtIHJlYWRSRFMoZmlsZSA9IHJlY291bnQuYi5maWxlKQpgYGAKCiMjIFJ1biBNQ1Bjb3VudGVyCgpgYGB7cn0KbWNwLnJlc3VsdHMgPC0gCiAgTUNQY291bnRlcjo6TUNQY291bnRlci5lc3RpbWF0ZShleHBycy5tYXQsIGZlYXR1cmVzVHlwZSA9ICJIVUdPX3N5bWJvbHMiKQptY3AubWVsdCA8LSByZXNoYXBlMjo6bWVsdChtY3AucmVzdWx0cywgdmFybmFtZXMgPSBjKCJDZWxsX3R5cGUiLCAiU2FtcGxlIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlLm5hbWUgPSAiTUNQX2VzdGltYXRlIikKcmVhZHI6OndyaXRlX3RzdihtY3AubWVsdCwgCiAgICAgICAgICAgICAgICAgZmlsZS5wYXRoKHJlc3VsdHMuZGlyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5BUkVTX0NvbUJhdF9NQ1BDb3VudGVyX3Jlc3VsdHNfdGlkeS50c3YiKSkKYGBgCgojIyBDb21wYXJlIHRvIFBMSUVSIExWcwoKYE5BUkVTIExWM2Agd2FzIHRoZSBuZXV0cm9waGlsLWFzc29jaWF0ZWQgTFYgaW4gdGhlIE5BUkVTIFBMSUVSIG1vZGVsOyBpdHMKYmVzdCBtYXRjaCBpcyBgcmVjb3VudCBMVjYwM2Agb24gdGhlIGJhc2lzIG9mIHRoZSBgWmAgbWF0cmljZXMgCihgMTMtY29tcGFyZV9OQVJFU19CYCkuIApgcmVjb3VudCBMVjYwM2AgYWxzbyBhcHBlYXJzIHRvIGJlIHByZXR0eSBuZXV0cm9waGlsLXNwZWNpZmljIChub3Qgc2lnbmlmaWNhbnRseQphc3NvY2lhdGVkIHdpdGggb3RoZXIgbXllbG9pZCBjZWxsIHR5cGVzOyBgMDctc2xlX2NlbGxfdHlwZV9yZWNvdW50Ml9tb2RlbGApLiAKVGhlc2UgYXJlIHRoZSBMVnMgd2UnbGwgY29tcGFyZSB0byB0aGUgTUNQY291bnRlciBlc3RpbWF0ZXMuCgojIyMgRGF0YSB3cmFuZ2xpbmcKCmBgYHtyfQojIHRpZHkgbmV1dHJvcGhpbC1hc3NvY2lhdGVkIExWcwpuZXV0cm8ubHYuZGYgPC0gYXMuZGF0YS5mcmFtZShuYXJlcy5iWyIzLElSSVNfTmV1dHJvcGhpbC1SZXN0aW5nIiwgXSkKbmV1dHJvLmx2LmRmIDwtIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKG5ldXRyby5sdi5kZikKY29sbmFtZXMobmV1dHJvLmx2LmRmKSA8LSBjKCJTYW1wbGUiLCAiTkFSRVNfTFYzIikKcmVjb3VudC5sdi5kZiA8LSBhcy5kYXRhLmZyYW1lKHJlY291bnQuYlsiNjAzLFNWTSBOZXV0cm9waGlscyIsIF0pCnJlY291bnQubHYuZGYgPC0gdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4ocmVjb3VudC5sdi5kZikKY29sbmFtZXMocmVjb3VudC5sdi5kZikgPC0gYygiU2FtcGxlIiwgInJlY291bnRfTFY2MDMiKQpuZXV0cm8ubHYuZGYgPC0gZHBseXI6OmlubmVyX2pvaW4obmV1dHJvLmx2LmRmLCByZWNvdW50Lmx2LmRmLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gIlNhbXBsZSIpCmBgYApgYGB7cn0KaGVhZChuZXV0cm8ubHYuZGYpCmBgYAoKYGBge3J9CiMgam9pbiB3aXRoIE1DUGNvdW50ZXIgbmV1dHJvcGhpbCBlc3RpbWF0ZXMKbmV1dHJvLmRmIDwtIGRwbHlyOjpmaWx0ZXIobWNwLm1lbHQsIENlbGxfdHlwZSA9PSAiTmV1dHJvcGhpbHMiKSAlPiUKICAgICAgICAgICAgICAgIGRwbHlyOjppbm5lcl9qb2luKHkgPSBuZXV0cm8ubHYuZGYsIGJ5ID0gIlNhbXBsZSIpCm5ldXRyby5maWxlIDwtIGZpbGUucGF0aChyZXN1bHRzLmRpciwgIk5BUkVTX25ldXRyb3BoaWxfTFZfbWNwX2FsbC50c3YiKQpyZWFkcjo6d3JpdGVfdHN2KG5ldXRyby5kZiwgcGF0aCA9IG5ldXRyby5maWxlKQpgYGAKCiMjIyBQbG90dGluZwoKYGBge3J9CnN1bW1hcnkobG0obmV1dHJvLmRmJE1DUF9lc3RpbWF0ZSB+IG5ldXRyby5kZiROQVJFU19MVjMpKQpgYGAKCmBgYHtyfQpuYXJlcy5wIDwtIG5ldXRyby5kZiAlPiUKICBnZ3Bsb3QyOjpnZ3Bsb3QoZ2dwbG90Mjo6YWVzKHggPSBOQVJFU19MVjMsIHkgPSBNQ1BfZXN0aW1hdGUpKSArCiAgZ2dwbG90Mjo6Z2VvbV9wb2ludChhbHBoYSA9IDAuNykgKwogIGdncGxvdDI6Omdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsKICBnZ3Bsb3QyOjp0aGVtZV9idygpICsKICBnZ3Bsb3QyOjpsYWJzKHRpdGxlID0gIk5BUkVTIFBMSUVSIG1vZGVsIiwKICAgICAgICAgICAgICAgIHkgPSAiTUNQY291bnRlciBOZXV0cm9waGlsIEVzdGltYXRlIiwKICAgICAgICAgICAgICAgIHggPSAiTkFSRVMgTFYzIikgKwogIGdncGxvdDI6OnRoZW1lKHBsb3QudGl0bGUgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgICAgICAgICAgIHRleHQgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoc2l6ZSA9IDE1KSkgKwogIGdncGxvdDI6OmFubm90YXRlKGdlb20gPSAidGV4dCIsIHggPSAtMC4xLCB5ID0gMi4yNSwgCiAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSAici1zcXVhcmVkID0gMC45OCIsIHNpemUgPSA1KQpuYXJlcy5wCmBgYAoKYGBge3J9CnN1bW1hcnkobG0obmV1dHJvLmRmJE1DUF9lc3RpbWF0ZSB+IG5ldXRyby5kZiRyZWNvdW50X0xWNjAzKSkKYGBgCgpgYGB7cn0KcmVjb3VudC5wIDwtIG5ldXRyby5kZiAlPiUKICBnZ3Bsb3QyOjpnZ3Bsb3QoZ2dwbG90Mjo6YWVzKHggPSByZWNvdW50X0xWNjAzLCB5ID0gTUNQX2VzdGltYXRlKSkgKwogIGdncGxvdDI6Omdlb21fcG9pbnQoYWxwaGEgPSAwLjcpICsKICBnZ3Bsb3QyOjpnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKSArCiAgZ2dwbG90Mjo6dGhlbWVfYncoKSArCiAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJyZWNvdW50MiBQTElFUiBtb2RlbCIsCiAgICAgICAgICAgICAgICB5ID0gIk1DUGNvdW50ZXIgTmV1dHJvcGhpbCBFc3RpbWF0ZSIsCiAgICAgICAgICAgICAgICB4ID0gInJlY291bnQgTFY2MDMiKSArCiAgZ2dwbG90Mjo6dGhlbWUocGxvdC50aXRsZSA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgICAgICAgICAgdGV4dCA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChzaXplID0gMTUpKSArCiAgZ2dwbG90Mjo6YW5ub3RhdGUoZ2VvbSA9ICJ0ZXh0IiwgeCA9IC0wLjEsIHkgPSAyLjUsIAogICAgICAgICAgICAgICAgICAgIGxhYmVsID0gInItc3F1YXJlZCA9IDAuOTAiLCBzaXplID0gNSkKcmVjb3VudC5wCmBgYAoKYGBge3J9CnBkZihmaWxlLnBhdGgocGxvdC5kaXIsICJOQVJFU19NQ1Bjb3VudGVyX21vZGVsX2NvbXBhcmlzb24ucGRmIiksIHdpZHRoID0gMTQsCiAgICBoZWlnaHQgPSA3KQpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShuYXJlcy5wLCByZWNvdW50LnAsIG5jb2wgPSAyKQpkZXYub2ZmKCkKYGBgCg==