Count Quality Control
Compares total mapped vs counted reads.
The Mapped vs Counted Reads
plot does not include external
counts.
Consider removing samples with too low or too high size factors.
bam_coverage <- fread(snakemake@input$bam_cov)
bam_coverage[, sampleID := as.character(sampleID)]
setnames(bam_coverage, 'record_count', 'total_count')
coverage_dt <- merge(bam_coverage,
data.table(sampleID = colnames(ods),
read_count = colSums(cnts_mtx),
isExternal = ods@colData$isExternal),
by = "sampleID", sort = FALSE)
# read counts
coverage_dt[, count_rank := rank(read_count)]
# size factors
ods <- estimateSizeFactors(ods)
coverage_dt[, size_factors := round(sizeFactors(ods), 3)]
coverage_dt[, sf_rank := rank(size_factors)]
# Show this plot only if there are external samples, as the next plot contains the same info
if(has_external == T){
p_depth <- ggplot(coverage_dt, aes(x = count_rank, y = read_count, col = isExternal)) +
geom_point(size = 3,show.legend = has_external) +
theme_cowplot() + background_grid() +
labs(title = "Obtained Read Counts", x="Sample Rank", y = "Counted Reads") +
ylim(c(0,NA)) +
scale_color_brewer(palette="Dark2")
p_depth
}
p_comp <- ggplot(coverage_dt[isExternal == F], aes(x = total_count, y = read_count)) +
geom_point(size = 3, show.legend = has_external, color = "#1B9E77") +
theme_cowplot() + background_grid() +
labs(title = "Total mapped vs. Counted Reads", x = "Mapped Reads", y = "Counted Reads") +
xlim(c(0,NA)) + ylim(c(0,NA))
p_comp
# ggExtra::ggMarginal(p_comp, type = "histogram") # could be a nice add-on
p_sf <- ggplot(coverage_dt, aes(sf_rank, size_factors, col = isExternal)) +
geom_point(size = 3,show.legend = has_external) +
ylim(c(0,NA)) +
theme_cowplot() + background_grid() +
labs(title = 'Size Factors', x = 'Sample Rank', y = 'Size Factors') +
scale_color_brewer(palette="Dark2")
p_sf
setnames(coverage_dt, old = c('total_count', 'read_count', 'size_factors'),
new = c('Reads Mapped', 'Reads Counted', 'Size Factors'))
DT::datatable(coverage_dt[, .(sampleID, `Reads Mapped`, `Reads Counted`, `Size Factors`)][order(`Reads Mapped`)],
caption = 'Reads summary statistics')
Filtering
local: A pre-filtered summary of counts using only
the local (from BAM) counts. Omitted if no external counts
all: A pre-filtered summary of counts using only the
merged local (from BAM) and external counts
passed FPKM: Passes the user defined FPKM cutoff in at
least 5% of genes
min 1 read: minimum of 1 read expressed in 5% of
genes
min 10 reads: minimum of 10 reads expressed in 5% of
genes
quant <- .95
if(has_external){
filter_mtx <- list(
local = cnts_mtx_local,
all = cnts_mtx,
`passed FPKM` = cnts_mtx[rowData(ods)$passedFilter,],
`min 1 read` = cnts_mtx[rowQuantiles(cnts_mtx, probs = quant) > 1, ],
`min 10 reads` = cnts_mtx[rowQuantiles(cnts_mtx, probs = quant) > 10, ]
)
filter_dt <- lapply(names(filter_mtx), function(filter_name) {
mtx <- filter_mtx[[filter_name]]
data.table(gene_ID = rownames(mtx), median_counts = rowMeans(mtx), filter = filter_name)
}) %>% rbindlist
filter_dt[, filter := factor(filter, levels = c('local', 'all', 'passed FPKM', 'min 1 read', 'min 10 reads'))]
} else{
filter_mtx <- list(
all = cnts_mtx,
`passed FPKM` = cnts_mtx[rowData(ods)$passedFilter,],
`min 1 read` = cnts_mtx[rowQuantiles(cnts_mtx, probs = quant) > 1, ],
`min 10 reads` = cnts_mtx[rowQuantiles(cnts_mtx, probs = quant) > 10, ]
)
filter_dt <- lapply(names(filter_mtx), function(filter_name) {
mtx <- filter_mtx[[filter_name]]
data.table(gene_ID = rownames(mtx), median_counts = rowMeans(mtx), filter = filter_name)
}) %>% rbindlist
filter_dt[, filter := factor(filter, levels = c('all', 'passed FPKM', 'min 1 read', 'min 10 reads'))]
}
binwidth <- .2
p_hist <- ggplot(filter_dt, aes(x = median_counts, fill = filter)) +
geom_histogram(binwidth = binwidth) +
scale_x_log10() +
facet_wrap(.~filter) +
labs(x = "Mean counts per gene", y = "Frequency", title = 'Mean Count Distribution') +
guides(col = guide_legend(title = NULL)) +
scale_fill_brewer(palette = "Paired") +
theme_cowplot() +
theme(legend.position = "none")
p_dens <- ggplot(filter_dt, aes(x = median_counts, col = filter)) +
geom_density(aes(y=binwidth * ..count..), size = 1.2) +
scale_x_log10() +
labs(x = "Mean counts per gene", y = "Frequency") +
guides(col = guide_legend(title = NULL)) +
scale_color_brewer(palette = "Paired") +
theme_cowplot() +
theme(legend.position = "top",
legend.justification="center",
legend.background = element_rect(color = NA))
plot_grid(p_hist, p_dens)
Expressed Genes
exp_genes_cols <- c(Rank = "expressedGenesRank",`Expressed\ngenes` = "expressedGenes",
`Union of\nexpressed genes` = "unionExpressedGenes",
`Intersection of\nexpressed genes` = "intersectionExpressedGenes",
`Genes passed\nfiltering` = "passedFilterGenes", `Is External` = "isExternal")
expressed_genes <- as.data.table(colData(ods)[,exp_genes_cols], keep.rownames = TRUE)
colnames(expressed_genes) <- c('RNA_ID', names(exp_genes_cols))
plotExpressedGenes(ods) +
theme_cowplot() +
background_grid(major = "y") +
geom_point(data = melt(expressed_genes, id.vars = c("RNA_ID", "Rank", "Is External")),
aes(Rank, value, col = variable, shape = `Is External`),
show.legend = has_external)
if(has_external){
DT::datatable(expressed_genes[order(Rank)],rownames = F)
} else{
DT::datatable(expressed_genes[order(Rank),-"Is External"],rownames = F)
}
Considerations: The verifying of the samples sex is
performed by comparing the expression levels of the genes XIST and UTY.
In general, females should have high XIST and low UTY expression, and
viceversa for males. For it to work, the sample annotation must have a
column called ‘SEX’, with values male/female. If some other values
exist, e.g., unknown, they will be matched. The prediction is performed
via a linear discriminant analysis on the log2 counts.
# Get sex column and proceed if it exists
sex_idx <- which('SEX' == toupper(colnames(colData(ods))))
if(isEmpty(sex_idx)){
print('Sex column not found in sample annotation')
} else{
# Verify if both XIST and UTY were counted
xist_id <- 'XIST'
uty_id <- 'UTY'
if(grepl('ENSG0', rownames(ods)[1])){
xist_id <- 'ENSG00000229807'
uty_id <- 'ENSG00000183878'
}
xist_idx <- grep(xist_id, rownames(ods))
uty_idx <- grep(uty_id, rownames(ods))
if(isEmpty(xist_idx) | isEmpty(uty_idx)){
print('Either XIST or UTY is not expressed')
}else{
sex_counts <- counts(ods)[c(xist_idx, uty_idx), ]
sex_dt <- data.table(sampleID = colnames(ods),
XIST = counts(ods)[xist_idx,],
UTY = counts(ods)[uty_idx,])
sex_dt <- merge(sex_dt, as.data.table(colData(ods))[,c(1, sex_idx), with = F], sort = F)
colnames(sex_dt) <- toupper(colnames(sex_dt))
sex_dt[, SEX := tolower(SEX)]
sex_dt[is.na(SEX), SEX := '']
# Train only in male/female in case there are other values
train_dt <- sex_dt[SEX %in% c('f', 'm', 'female', 'male')]
library("MASS")
lda <- lda(SEX ~ log2(XIST+1) + log2(UTY+1), data = train_dt)
sex_dt[, predicted_sex := predict(lda, sex_dt)$class]
sex_dt[, match_sex := SEX == predicted_sex]
table(sex_dt[, .(SEX, predicted_sex)])
g <- ggplot(sex_dt, aes(XIST+1, UTY+1)) +
geom_point(aes(col = SEX, shape = predicted_sex, alpha = match_sex)) +
scale_x_log10(limits = c(1,NA)) + scale_y_log10(limits = c(1,NA)) +
scale_alpha_manual(values = c(1, .1)) +
theme_cowplot() + background_grid(major = 'xy', minor = 'xy') +
annotation_logticks(sides = 'bl') +
labs(color = 'Sex', shape = 'Predicted sex', alpha = 'Matches sex')
plot(g)
DT::datatable(sex_dt[match_sex == F], caption = 'Sex mismatches')
# Write table
fwrite(sex_dt, gsub('ods_unfitted.Rds', 'xist_uty.tsv', snakemake@input$ods),
sep = '\t', quote = F)
}
}
IyctLS0KIycgdGl0bGU6ICJDb3VudHMgU3VtbWFyeTogYHIgcGFzdGUoc25ha2VtYWtlQHdpbGRjYXJkcyRkYXRhc2V0LCBzbmFrZW1ha2VAd2lsZGNhcmRzJGFubm90YXRpb24sIHNlcCA9ICctLScpYCIKIycgYXV0aG9yOiAKIycgd2I6CiMnICBsb2c6CiMnICAgLSBzbmFrZW1ha2U6ICdgc20gc3RyKHRtcF9kaXIgLyAiQUUiIC8gInthbm5vdGF0aW9ufSIgLyAie2RhdGFzZXR9IiAvICJjb3VudF9zdW1tYXJ5LlJkcyIpYCcKIycgIGlucHV0OiAKIycgICAgLSBvZHM6ICdgc20gY2ZnLmdldFByb2Nlc3NlZFJlc3VsdHNEaXIoKSArCiMnICAgICAgICAgICAgIi9hYmVycmFudF9leHByZXNzaW9uL3thbm5vdGF0aW9ufS9vdXRyaWRlci97ZGF0YXNldH0vb2RzX3VuZml0dGVkLlJkcyJgJwojJyAgICAtIGJhbV9jb3Y6ICdgc20gcnVsZXMuYWJlcnJhbnRFeHByZXNzaW9uX21lcmdlQmFtU3RhdHMub3V0cHV0YCcKIycgIG91dHB1dDoKIycgICAtIHdCaHRtbDogJ2BzbSBjb25maWdbImh0bWxPdXRwdXRQYXRoIl0gKwojJyAgICAgICAgICAgICAgIi9BYmVycmFudEV4cHJlc3Npb24vQ291bnRpbmcve2Fubm90YXRpb259L1N1bW1hcnlfe2RhdGFzZXR9Lmh0bWwiYCcKIycgIHR5cGU6IG5vaW5kZXgKIycgb3V0cHV0OgojJyAgaHRtbF9kb2N1bWVudDoKIycgICBjb2RlX2ZvbGRpbmc6IGhpZGUKIycgICBjb2RlX2Rvd25sb2FkOiBUUlVFCiMnLS0tCgpzYXZlUkRTKHNuYWtlbWFrZSwgc25ha2VtYWtlQGxvZyRzbmFrZW1ha2UpCgpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkoT1VUUklERVIpCiAgbGlicmFyeShTdW1tYXJpemVkRXhwZXJpbWVudCkKICBsaWJyYXJ5KEdlbm9taWNBbGlnbm1lbnRzKQogIGxpYnJhcnkoZ2dwbG90MikKICBsaWJyYXJ5KGdndGhlbWVzKQogIGxpYnJhcnkoY293cGxvdCkKICBsaWJyYXJ5KGRhdGEudGFibGUpCiAgbGlicmFyeSh0aWR5cikKfSkKCm9kcyA8LSByZWFkUkRTKHNuYWtlbWFrZUBpbnB1dCRvZHMpCgpoYXNfZXh0ZXJuYWwgPC0gYW55KGFzLmxvZ2ljYWwoY29sRGF0YShvZHMpJGlzRXh0ZXJuYWwpKQpjbnRzX210eF9sb2NhbCA8LSBjb3VudHMob2RzLCBub3JtYWxpemVkID0gRilbLCFhcy5sb2dpY2FsKG9kc0Bjb2xEYXRhJGlzRXh0ZXJuYWwpLGRyb3A9RkFMU0VdCmNudHNfbXR4IDwtIGNvdW50cyhvZHMsIG5vcm1hbGl6ZWQgPSBGKQoKIycgIyMgTnVtYmVyIG9mIHNhbXBsZXM6ICAKIycgTG9jYWw6IGByIHN1bSghYXMubG9naWNhbChvZHNAY29sRGF0YSRpc0V4dGVybmFsKSlgICAKIycgRXh0ZXJuYWw6IGByIHN1bShhcy5sb2dpY2FsKG9kc0Bjb2xEYXRhJGlzRXh0ZXJuYWwpKWAgIAojJyAKIycgIyBDb3VudCBRdWFsaXR5IENvbnRyb2wKIycgCiMnIENvbXBhcmVzIHRvdGFsIG1hcHBlZCB2cyBjb3VudGVkIHJlYWRzLiAgCiMnIFRoZSBgTWFwcGVkIHZzIENvdW50ZWQgUmVhZHNgIHBsb3QgZG9lcyBub3QgaW5jbHVkZSBleHRlcm5hbCBjb3VudHMuICAKIycgQ29uc2lkZXIgcmVtb3Zpbmcgc2FtcGxlcyB3aXRoIHRvbyBsb3cgb3IgdG9vIGhpZ2ggc2l6ZSBmYWN0b3JzLgojJyAgCmJhbV9jb3ZlcmFnZSA8LSBmcmVhZChzbmFrZW1ha2VAaW5wdXQkYmFtX2NvdikKYmFtX2NvdmVyYWdlWywgc2FtcGxlSUQgOj0gYXMuY2hhcmFjdGVyKHNhbXBsZUlEKV0Kc2V0bmFtZXMoYmFtX2NvdmVyYWdlLCAncmVjb3JkX2NvdW50JywgJ3RvdGFsX2NvdW50JykKY292ZXJhZ2VfZHQgPC0gbWVyZ2UoYmFtX2NvdmVyYWdlLAogICAgICAgICAgICAgICAgICAgICBkYXRhLnRhYmxlKHNhbXBsZUlEID0gY29sbmFtZXMob2RzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWFkX2NvdW50ID0gY29sU3VtcyhjbnRzX210eCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXNFeHRlcm5hbCA9IG9kc0Bjb2xEYXRhJGlzRXh0ZXJuYWwpLAogICAgICAgICAgICAgICAgICAgICBieSA9ICJzYW1wbGVJRCIsIHNvcnQgPSBGQUxTRSkKIyByZWFkIGNvdW50cwpjb3ZlcmFnZV9kdFssIGNvdW50X3JhbmsgOj0gcmFuayhyZWFkX2NvdW50KV0KCiMgc2l6ZSBmYWN0b3JzIApvZHMgPC0gZXN0aW1hdGVTaXplRmFjdG9ycyhvZHMpCmNvdmVyYWdlX2R0Wywgc2l6ZV9mYWN0b3JzIDo9IHJvdW5kKHNpemVGYWN0b3JzKG9kcyksIDMpXQpjb3ZlcmFnZV9kdFssIHNmX3JhbmsgOj0gcmFuayhzaXplX2ZhY3RvcnMpXQoKIyBTaG93IHRoaXMgcGxvdCBvbmx5IGlmIHRoZXJlIGFyZSBleHRlcm5hbCBzYW1wbGVzLCBhcyB0aGUgbmV4dCBwbG90IGNvbnRhaW5zIHRoZSBzYW1lIGluZm8KaWYoaGFzX2V4dGVybmFsID09IFQpewogIHBfZGVwdGggPC0gZ2dwbG90KGNvdmVyYWdlX2R0LCBhZXMoeCA9IGNvdW50X3JhbmssIHkgPSByZWFkX2NvdW50LCBjb2wgPSBpc0V4dGVybmFsKSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gMyxzaG93LmxlZ2VuZCA9IGhhc19leHRlcm5hbCkgKwogICAgdGhlbWVfY293cGxvdCgpICsgYmFja2dyb3VuZF9ncmlkKCkgKwogICAgbGFicyh0aXRsZSA9ICJPYnRhaW5lZCBSZWFkIENvdW50cyIsIHg9IlNhbXBsZSBSYW5rIiwgeSA9ICJDb3VudGVkIFJlYWRzIikgKwogICAgeWxpbShjKDAsTkEpKSArCiAgICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iRGFyazIiKQogIHBfZGVwdGgKfQoKCnBfY29tcCA8LSBnZ3Bsb3QoY292ZXJhZ2VfZHRbaXNFeHRlcm5hbCA9PSBGXSwgYWVzKHggPSB0b3RhbF9jb3VudCwgeSA9IHJlYWRfY291bnQpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMywgc2hvdy5sZWdlbmQgPSBoYXNfZXh0ZXJuYWwsIGNvbG9yID0gIiMxQjlFNzciKSArCiAgdGhlbWVfY293cGxvdCgpICsgYmFja2dyb3VuZF9ncmlkKCkgKwogIGxhYnModGl0bGUgPSAiVG90YWwgbWFwcGVkIHZzLiBDb3VudGVkIFJlYWRzIiwgeCA9ICJNYXBwZWQgUmVhZHMiLCB5ID0gIkNvdW50ZWQgUmVhZHMiKSArCiAgeGxpbShjKDAsTkEpKSArIHlsaW0oYygwLE5BKSkKcF9jb21wCiMgZ2dFeHRyYTo6Z2dNYXJnaW5hbChwX2NvbXAsIHR5cGUgPSAiaGlzdG9ncmFtIikgIyBjb3VsZCBiZSBhIG5pY2UgYWRkLW9uCgpwX3NmIDwtIGdncGxvdChjb3ZlcmFnZV9kdCwgYWVzKHNmX3JhbmssIHNpemVfZmFjdG9ycywgY29sID0gaXNFeHRlcm5hbCkpICsKICBnZW9tX3BvaW50KHNpemUgPSAzLHNob3cubGVnZW5kID0gaGFzX2V4dGVybmFsKSArCiAgeWxpbShjKDAsTkEpKSArCiAgdGhlbWVfY293cGxvdCgpICsgYmFja2dyb3VuZF9ncmlkKCkgKwogIGxhYnModGl0bGUgPSAnU2l6ZSBGYWN0b3JzJywgeCA9ICdTYW1wbGUgUmFuaycsIHkgPSAnU2l6ZSBGYWN0b3JzJykgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJEYXJrMiIpCgpwX3NmCgpzZXRuYW1lcyhjb3ZlcmFnZV9kdCwgb2xkID0gYygndG90YWxfY291bnQnLCAncmVhZF9jb3VudCcsICdzaXplX2ZhY3RvcnMnKSwKICAgICAgICAgbmV3ID0gYygnUmVhZHMgTWFwcGVkJywgJ1JlYWRzIENvdW50ZWQnLCAnU2l6ZSBGYWN0b3JzJykpCkRUOjpkYXRhdGFibGUoY292ZXJhZ2VfZHRbLCAuKHNhbXBsZUlELCBgUmVhZHMgTWFwcGVkYCwgYFJlYWRzIENvdW50ZWRgLCBgU2l6ZSBGYWN0b3JzYCldW29yZGVyKGBSZWFkcyBNYXBwZWRgKV0sCiAgICAgICAgICAgICAgY2FwdGlvbiA9ICdSZWFkcyBzdW1tYXJ5IHN0YXRpc3RpY3MnKQoKIycgIyBGaWx0ZXJpbmcKIycgKipsb2NhbCoqOiBBIHByZS1maWx0ZXJlZCBzdW1tYXJ5IG9mIGNvdW50cyB1c2luZyBvbmx5IHRoZSBsb2NhbCAoZnJvbSBCQU0pIGNvdW50cy4gT21pdHRlZCBpZiBubyBleHRlcm5hbCBjb3VudHMgIAojJyAqKmFsbCoqOiBBIHByZS1maWx0ZXJlZCBzdW1tYXJ5IG9mIGNvdW50cyB1c2luZyBvbmx5IHRoZSBtZXJnZWQgbG9jYWwgKGZyb20gQkFNKSBhbmQgZXh0ZXJuYWwgY291bnRzICAKIycgKipwYXNzZWQgRlBLTSoqOiBQYXNzZXMgdGhlIHVzZXIgZGVmaW5lZCBGUEtNIGN1dG9mZiBpbiBhdCBsZWFzdCA1JSBvZiBnZW5lcyAgCiMnICoqbWluIDEgcmVhZCoqOiBtaW5pbXVtIG9mIDEgcmVhZCBleHByZXNzZWQgaW4gNSUgb2YgZ2VuZXMgIAojJyAqKm1pbiAxMCByZWFkcyoqOiBtaW5pbXVtIG9mIDEwIHJlYWRzIGV4cHJlc3NlZCBpbiA1JSBvZiBnZW5lcyAgCgpxdWFudCA8LSAuOTUKCmlmKGhhc19leHRlcm5hbCl7CiAgICBmaWx0ZXJfbXR4IDwtIGxpc3QoCiAgICAgIGxvY2FsID0gY250c19tdHhfbG9jYWwsCiAgICAgIGFsbCA9IGNudHNfbXR4LAogICAgICBgcGFzc2VkIEZQS01gID0gY250c19tdHhbcm93RGF0YShvZHMpJHBhc3NlZEZpbHRlcixdLAogICAgICBgbWluIDEgcmVhZGAgPSBjbnRzX210eFtyb3dRdWFudGlsZXMoY250c19tdHgsIHByb2JzID0gcXVhbnQpID4gMSwgXSwKICAgICAgYG1pbiAxMCByZWFkc2AgPSBjbnRzX210eFtyb3dRdWFudGlsZXMoY250c19tdHgsIHByb2JzID0gcXVhbnQpID4gMTAsIF0KICAgICkKICAgIGZpbHRlcl9kdCA8LSBsYXBwbHkobmFtZXMoZmlsdGVyX210eCksIGZ1bmN0aW9uKGZpbHRlcl9uYW1lKSB7CiAgICAgIG10eCA8LSBmaWx0ZXJfbXR4W1tmaWx0ZXJfbmFtZV1dCiAgICAgIGRhdGEudGFibGUoZ2VuZV9JRCA9IHJvd25hbWVzKG10eCksIG1lZGlhbl9jb3VudHMgPSByb3dNZWFucyhtdHgpLCBmaWx0ZXIgPSBmaWx0ZXJfbmFtZSkKICAgIH0pICU+JSByYmluZGxpc3QKICAgIGZpbHRlcl9kdFssIGZpbHRlciA6PSBmYWN0b3IoZmlsdGVyLCBsZXZlbHMgPSBjKCdsb2NhbCcsICdhbGwnLCAncGFzc2VkIEZQS00nLCAnbWluIDEgcmVhZCcsICdtaW4gMTAgcmVhZHMnKSldCn0gZWxzZXsKICAgIGZpbHRlcl9tdHggPC0gbGlzdCgKICAgICAgYWxsID0gY250c19tdHgsCiAgICAgIGBwYXNzZWQgRlBLTWAgPSBjbnRzX210eFtyb3dEYXRhKG9kcykkcGFzc2VkRmlsdGVyLF0sCiAgICAgIGBtaW4gMSByZWFkYCA9IGNudHNfbXR4W3Jvd1F1YW50aWxlcyhjbnRzX210eCwgcHJvYnMgPSBxdWFudCkgPiAxLCBdLAogICAgICBgbWluIDEwIHJlYWRzYCA9IGNudHNfbXR4W3Jvd1F1YW50aWxlcyhjbnRzX210eCwgcHJvYnMgPSBxdWFudCkgPiAxMCwgXQogICAgKQogICAgZmlsdGVyX2R0IDwtIGxhcHBseShuYW1lcyhmaWx0ZXJfbXR4KSwgZnVuY3Rpb24oZmlsdGVyX25hbWUpIHsKICAgICAgbXR4IDwtIGZpbHRlcl9tdHhbW2ZpbHRlcl9uYW1lXV0KICAgICAgZGF0YS50YWJsZShnZW5lX0lEID0gcm93bmFtZXMobXR4KSwgbWVkaWFuX2NvdW50cyA9IHJvd01lYW5zKG10eCksIGZpbHRlciA9IGZpbHRlcl9uYW1lKQogICAgfSkgJT4lIHJiaW5kbGlzdAogICAgZmlsdGVyX2R0WywgZmlsdGVyIDo9IGZhY3RvcihmaWx0ZXIsIGxldmVscyA9IGMoJ2FsbCcsICdwYXNzZWQgRlBLTScsICdtaW4gMSByZWFkJywgJ21pbiAxMCByZWFkcycpKV0KfQoKYmlud2lkdGggPC0gLjIKcF9oaXN0IDwtIGdncGxvdChmaWx0ZXJfZHQsIGFlcyh4ID0gbWVkaWFuX2NvdW50cywgZmlsbCA9IGZpbHRlcikpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IGJpbndpZHRoKSArCiAgc2NhbGVfeF9sb2cxMCgpICsKICBmYWNldF93cmFwKC5+ZmlsdGVyKSArCiAgbGFicyh4ID0gIk1lYW4gY291bnRzIHBlciBnZW5lIiwgeSA9ICJGcmVxdWVuY3kiLCB0aXRsZSA9ICdNZWFuIENvdW50IERpc3RyaWJ1dGlvbicpICsKICBndWlkZXMoY29sID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gTlVMTCkpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhaXJlZCIpICsKICB0aGVtZV9jb3dwbG90KCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCnBfZGVucyA8LSBnZ3Bsb3QoZmlsdGVyX2R0LCBhZXMoeCA9IG1lZGlhbl9jb3VudHMsIGNvbCA9IGZpbHRlcikpICsKICBnZW9tX2RlbnNpdHkoYWVzKHk9Ymlud2lkdGggKiAuLmNvdW50Li4pLCBzaXplID0gMS4yKSArCiAgc2NhbGVfeF9sb2cxMCgpICsKICBsYWJzKHggPSAiTWVhbiBjb3VudHMgcGVyIGdlbmUiLCB5ID0gIkZyZXF1ZW5jeSIpICsKICBndWlkZXMoY29sID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gTlVMTCkpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJQYWlyZWQiKSArCiAgdGhlbWVfY293cGxvdCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIiwKICAgICAgICBsZWdlbmQuanVzdGlmaWNhdGlvbj0iY2VudGVyIiwKICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvciA9IE5BKSkKCiMrIG1lYW5Db3VudHMsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEyCnBsb3RfZ3JpZChwX2hpc3QsIHBfZGVucykKCiMnICMjIyBFeHByZXNzZWQgR2VuZXMKZXhwX2dlbmVzX2NvbHMgPC0gYyhSYW5rID0gImV4cHJlc3NlZEdlbmVzUmFuayIsYEV4cHJlc3NlZFxuZ2VuZXNgID0gImV4cHJlc3NlZEdlbmVzIiwgCiAgICAgICAgICAgICAgICAgICAgYFVuaW9uIG9mXG5leHByZXNzZWQgZ2VuZXNgID0gInVuaW9uRXhwcmVzc2VkR2VuZXMiLCAKICAgICAgICAgICAgICAgICAgICBgSW50ZXJzZWN0aW9uIG9mXG5leHByZXNzZWQgZ2VuZXNgID0gImludGVyc2VjdGlvbkV4cHJlc3NlZEdlbmVzIiwgCiAgICAgICAgICAgICAgICAgICAgYEdlbmVzIHBhc3NlZFxuZmlsdGVyaW5nYCA9ICJwYXNzZWRGaWx0ZXJHZW5lcyIsIGBJcyBFeHRlcm5hbGAgPSAiaXNFeHRlcm5hbCIpCgpleHByZXNzZWRfZ2VuZXMgPC0gYXMuZGF0YS50YWJsZShjb2xEYXRhKG9kcylbLGV4cF9nZW5lc19jb2xzXSwga2VlcC5yb3duYW1lcyA9IFRSVUUpCmNvbG5hbWVzKGV4cHJlc3NlZF9nZW5lcykgPC0gYygnUk5BX0lEJywgbmFtZXMoZXhwX2dlbmVzX2NvbHMpKQoKIysgZXhwcmVzc2VkR2VuZXMsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTgKcGxvdEV4cHJlc3NlZEdlbmVzKG9kcykgKyAKICB0aGVtZV9jb3dwbG90KCkgKwogIGJhY2tncm91bmRfZ3JpZChtYWpvciA9ICJ5IikgKwogIGdlb21fcG9pbnQoZGF0YSA9IG1lbHQoZXhwcmVzc2VkX2dlbmVzLCBpZC52YXJzID0gYygiUk5BX0lEIiwgIlJhbmsiLCAiSXMgRXh0ZXJuYWwiKSksCiAgICAgICAgICAgICBhZXMoUmFuaywgdmFsdWUsIGNvbCA9IHZhcmlhYmxlLCBzaGFwZSA9IGBJcyBFeHRlcm5hbGApLCAKICAgICAgICAgICAgIHNob3cubGVnZW5kID0gaGFzX2V4dGVybmFsKQoKaWYoaGFzX2V4dGVybmFsKXsKICAgIERUOjpkYXRhdGFibGUoZXhwcmVzc2VkX2dlbmVzW29yZGVyKFJhbmspXSxyb3duYW1lcyA9IEYpCn0gZWxzZXsKICAgIERUOjpkYXRhdGFibGUoZXhwcmVzc2VkX2dlbmVzW29yZGVyKFJhbmspLC0iSXMgRXh0ZXJuYWwiXSxyb3duYW1lcyA9IEYpCn0KCiMnICoqQ29uc2lkZXJhdGlvbnM6KioKIycgVGhlIHZlcmlmeWluZyBvZiB0aGUgc2FtcGxlcyBzZXggaXMgcGVyZm9ybWVkIGJ5IGNvbXBhcmluZyB0aGUgZXhwcmVzc2lvbiBsZXZlbHMgb2YgCiMnIHRoZSBnZW5lcyBYSVNUIGFuZCBVVFkuIEluIGdlbmVyYWwsIGZlbWFsZXMgc2hvdWxkIGhhdmUgaGlnaCBYSVNUIGFuZCBsb3cgVVRZIGV4cHJlc3Npb24sCiMnIGFuZCB2aWNldmVyc2EgZm9yIG1hbGVzLiBGb3IgaXQgdG8gd29yaywgdGhlIHNhbXBsZSBhbm5vdGF0aW9uIG11c3QgaGF2ZSBhIGNvbHVtbiBjYWxsZWQgJ1NFWCcsCiMnIHdpdGggdmFsdWVzIG1hbGUvZmVtYWxlLiBJZiBzb21lIG90aGVyIHZhbHVlcyBleGlzdCwgZS5nLiwgdW5rbm93biwgdGhleSB3aWxsIGJlIG1hdGNoZWQuIAojJyBUaGUgcHJlZGljdGlvbiBpcyBwZXJmb3JtZWQgdmlhIGEgbGluZWFyIGRpc2NyaW1pbmFudCBhbmFseXNpcyBvbiB0aGUgbG9nMiBjb3VudHMuCgojIEdldCBzZXggY29sdW1uIGFuZCBwcm9jZWVkIGlmIGl0IGV4aXN0cwpzZXhfaWR4IDwtIHdoaWNoKCdTRVgnID09IHRvdXBwZXIoY29sbmFtZXMoY29sRGF0YShvZHMpKSkpCmlmKGlzRW1wdHkoc2V4X2lkeCkpewogIHByaW50KCdTZXggY29sdW1uIG5vdCBmb3VuZCBpbiBzYW1wbGUgYW5ub3RhdGlvbicpCn0gZWxzZXsKICAKICAjIFZlcmlmeSBpZiBib3RoIFhJU1QgYW5kIFVUWSB3ZXJlIGNvdW50ZWQKICB4aXN0X2lkIDwtICdYSVNUJwogIHV0eV9pZCA8LSAnVVRZJwogIAogIGlmKGdyZXBsKCdFTlNHMCcsIHJvd25hbWVzKG9kcylbMV0pKXsKICAgIHhpc3RfaWQgPC0gJ0VOU0cwMDAwMDIyOTgwNycKICAgIHV0eV9pZCA8LSAnRU5TRzAwMDAwMTgzODc4JwogIH0KICB4aXN0X2lkeCA8LSBncmVwKHhpc3RfaWQsIHJvd25hbWVzKG9kcykpCiAgdXR5X2lkeCA8LSBncmVwKHV0eV9pZCwgcm93bmFtZXMob2RzKSkKICAKICBpZihpc0VtcHR5KHhpc3RfaWR4KSB8IGlzRW1wdHkodXR5X2lkeCkpewogICAgcHJpbnQoJ0VpdGhlciBYSVNUIG9yIFVUWSBpcyBub3QgZXhwcmVzc2VkJykKICB9ZWxzZXsKICAgIAogICAgc2V4X2NvdW50cyA8LSBjb3VudHMob2RzKVtjKHhpc3RfaWR4LCB1dHlfaWR4KSwgXQogICAgc2V4X2R0IDwtIGRhdGEudGFibGUoc2FtcGxlSUQgPSBjb2xuYW1lcyhvZHMpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIFhJU1QgPSBjb3VudHMob2RzKVt4aXN0X2lkeCxdLCAKICAgICAgICAgICAgICAgICAgICAgICAgIFVUWSA9IGNvdW50cyhvZHMpW3V0eV9pZHgsXSkKICAgIHNleF9kdCA8LSBtZXJnZShzZXhfZHQsIGFzLmRhdGEudGFibGUoY29sRGF0YShvZHMpKVssYygxLCBzZXhfaWR4KSwgd2l0aCA9IEZdLCBzb3J0ID0gRikKICAgIGNvbG5hbWVzKHNleF9kdCkgPC0gdG91cHBlcihjb2xuYW1lcyhzZXhfZHQpKQogICAgc2V4X2R0WywgU0VYIDo9IHRvbG93ZXIoU0VYKV0KICAgIHNleF9kdFtpcy5uYShTRVgpLCBTRVggOj0gJyddCiAgICAKICAgICMgVHJhaW4gb25seSBpbiBtYWxlL2ZlbWFsZSBpbiBjYXNlIHRoZXJlIGFyZSBvdGhlciB2YWx1ZXMKICAgIHRyYWluX2R0IDwtIHNleF9kdFtTRVggJWluJSBjKCdmJywgJ20nLCAnZmVtYWxlJywgJ21hbGUnKV0KICAgIAogICAgbGlicmFyeSgiTUFTUyIpCiAgICBsZGEgPC0gbGRhKFNFWCB+IGxvZzIoWElTVCsxKSArIGxvZzIoVVRZKzEpLCBkYXRhID0gdHJhaW5fZHQpCiAgICAKICAgIHNleF9kdFssIHByZWRpY3RlZF9zZXggOj0gcHJlZGljdChsZGEsIHNleF9kdCkkY2xhc3NdCiAgICBzZXhfZHRbLCBtYXRjaF9zZXggOj0gU0VYID09IHByZWRpY3RlZF9zZXhdCiAgICB0YWJsZShzZXhfZHRbLCAuKFNFWCwgcHJlZGljdGVkX3NleCldKQogICAgCiAgICBnIDwtIGdncGxvdChzZXhfZHQsIGFlcyhYSVNUKzEsIFVUWSsxKSkgKyAKICAgICAgZ2VvbV9wb2ludChhZXMoY29sID0gU0VYLCBzaGFwZSA9IHByZWRpY3RlZF9zZXgsIGFscGhhID0gbWF0Y2hfc2V4KSkgKyAKICAgICAgc2NhbGVfeF9sb2cxMChsaW1pdHMgPSBjKDEsTkEpKSArIHNjYWxlX3lfbG9nMTAobGltaXRzID0gYygxLE5BKSkgKwogICAgICBzY2FsZV9hbHBoYV9tYW51YWwodmFsdWVzID0gYygxLCAuMSkpICsgCiAgICAgIHRoZW1lX2Nvd3Bsb3QoKSArIGJhY2tncm91bmRfZ3JpZChtYWpvciA9ICd4eScsIG1pbm9yID0gJ3h5JykgKyAKICAgICAgYW5ub3RhdGlvbl9sb2d0aWNrcyhzaWRlcyA9ICdibCcpICsgCiAgICAgIGxhYnMoY29sb3IgPSAnU2V4Jywgc2hhcGUgPSAnUHJlZGljdGVkIHNleCcsIGFscGhhID0gJ01hdGNoZXMgc2V4JykKICAgIHBsb3QoZykKICAgIAogICAgRFQ6OmRhdGF0YWJsZShzZXhfZHRbbWF0Y2hfc2V4ID09IEZdLCBjYXB0aW9uID0gJ1NleCBtaXNtYXRjaGVzJykKICAgIAogICAgIyBXcml0ZSB0YWJsZQogICAgZndyaXRlKHNleF9kdCwgZ3N1Yignb2RzX3VuZml0dGVkLlJkcycsICd4aXN0X3V0eS50c3YnLCBzbmFrZW1ha2VAaW5wdXQkb2RzKSwgCiAgICAgICAgICAgc2VwID0gJ1x0JywgcXVvdGUgPSBGKQogIH0KfQo=