Background
The functions and code chunks collected here fullfill a very simple
purpose: to plot read coverage or related information, as well as gene
annotation as coverate tracks on one canvas. This is often very useful
for sequencing data that is created from large throughput pipelines, and
where it is not practical to import and plot coverage etc in a genome
browser such as IGV.
Libraries and test data
Packages
ggcoverage
can be installed with latest version from
github
devtools::install_github("showteeth/ggcoverage")
suppressPackageStartupMessages({
library(tidyverse)
library(GenomicFeatures)
library(GenomicRanges)
library(GenomicAlignments)
library(ggcoverage)
library(ggrepel)
library(cowplot)
})
Import utility functions
get_coverage
calculates coverage per nt (similar to
bedgraph files) from GAlignments
or
Granges
track_coverage_combined
plots one figure with different
tracks overlaid
track_coverage_separate
plots one figure with different
tracks stacked on each other
track_genomic_features
plots genome features from a
Granges
or GrangesList
check_gff
checks *.gff
files for
problematic tags and replaces those
source("../source/coverage_utils.R")
Import genome annotation
- first import the
*.gff
file with gene annotation
- has to be parsed by
GenomicFeatures
or
ORFik
packages to txdb
- then CDS, transcripts etc. can be extracted as
GRanges
object
# file path
genome_gff <- "../data/spyogenes_genome.gff"
# check gff file
check_gff(genome_gff, "Name=[a-zA-Z0-9\\s\\-\\_]*,", "Name=[a-zA-Z0-9\\s\\-\\_]*")
found no errors, no new GFF file exported
# import genome annotation
txdb <- makeTxDbFromGFF(genome_gff, format = "gff3")
Warning in call_fun_in_txdbmaker("makeTxDbFromGFF", ...): makeTxDbFromGFF() has moved to the txdbmaker package. Please call
txdbmaker::makeTxDbFromGFF() to get rid of this warning.
Import genomic features from the file as a GRanges object ...
OK
Prepare the 'metadata' data frame ... OK
Make the TxDb object ...
Warning in makeTxDbFromGRanges(gr, metadata = metadata): The following transcripts were dropped because their exon ranks could
not be inferred (either because the exons are not on the same
chromosome/strand or because they are not separated by introns):
gene-SPY_RS01020, gene-SPY_RS05570
OK
# extract all CDSs as GRanges object
list_cds <- transcripts(txdb)
head(list_cds)
GRanges object with 6 ranges and 2 metadata columns:
seqnames ranges strand | tx_id tx_name
<Rle> <IRanges> <Rle> | <integer> <character>
[1] NC_002737.2 232-1587 + | 1 SPY_RS00005
[2] NC_002737.2 1742-2878 + | 2 SPY_RS00010
[3] NC_002737.2 2953-3150 + | 3 SPY_RS00015
[4] NC_002737.2 3480-4595 + | 4 SPY_RS00020
[5] NC_002737.2 4665-5234 + | 5 SPY_RS00025
[6] NC_002737.2 5237-8740 + | 6 SPY_RS00030
-------
seqinfo: 1 sequence from an unspecified genome; no seqlengths
Import *.bam files
- next import example
*.bam
files with read data
- one RNASeq data file, first 5000 nt of genome
- one RiboSeq data file, first 5000 nt of genome
rnaseq <- readGAlignments("../data/spyogenes_rnaseq.bam")
riboseq <- readGAlignments("../data/spyogenes_riboseq.bam")
head(rnaseq)
GAlignments object with 6 alignments and 0 metadata columns:
seqnames strand cigar qwidth start end width
<Rle> <Rle> <character> <integer> <integer> <integer> <integer>
[1] NC_002737.2 + 21M 21 1 21 21
[2] NC_002737.2 + 21M 21 1 21 21
[3] NC_002737.2 + 22M 22 2 23 22
[4] NC_002737.2 + 22M 22 2 23 22
[5] NC_002737.2 + 17M 17 4 20 17
[6] NC_002737.2 + 19M 19 4 22 19
njunc
<integer>
[1] 0
[2] 0
[3] 0
[4] 0
[5] 0
[6] 0
-------
seqinfo: 1 sequence from an unspecified genome
- if read data is paired-end instead of single-end,
use
readGAlignmentsPairs
- note: does not work with this example data
rnaseq <- readGAlignmentPairs("../data/spyogenes_rnaseq.bam")
riboseq <- readGAlignmentPairs("../data/spyogenes_riboseq.bam")
Get coverage for *.bam files
- specify the appropriate data source with
format
(one of
“bam”, “BigWig”)
- specify a name for the track with
sample
- coverage is calculated per
strand
- a new variable
sample_strand
combines the two
df_coverage <- bind_rows(
get_coverage(rnaseq, format = "bam", sample = "RNA"),
get_coverage(riboseq, format = "bam", sample = "RIBO")
)
# turn sample names into factor for same order of tracks
df_coverage <- df_coverage %>%
mutate(sample_strand = factor(sample_strand, unique(sample_strand)))
# print
head(df_coverage)
Import *.bigwig files
- import
*.bigwig
/*.bw
as an alternative to
bam
- bigwig files are strand-specific binary (compressed) coverage
files
- therefore need two files per sample, and specify strand when
importing
- similar to
*.bedgraph
files
rnaseq_bw_for <- rtracklayer::import(con = "../data/spyogenes_rnaseq_forward.bw", format = "BigWig")
rnaseq_bw_rev <- rtracklayer::import(con = "../data/spyogenes_rnaseq_reverse.bw", format = "BigWig")
strand(rnaseq_bw_for) <- "+"
strand(rnaseq_bw_rev) <- "-"
head(rnaseq_bw_for)
GRanges object with 6 ranges and 1 metadata column:
seqnames ranges strand | score
<Rle> <IRanges> <Rle> | <numeric>
[1] NC_002737.2 1 + | 2
[2] NC_002737.2 2-3 + | 4
[3] NC_002737.2 4 + | 7
[4] NC_002737.2 5-7 + | 9
[5] NC_002737.2 8-20 + | 10
[6] NC_002737.2 21 + | 9
-------
seqinfo: 1 sequence from an unspecified genome
Get coverage for *.bigwig files
df_coverage_bw <- bind_rows(
get_coverage(rnaseq_bw_for, format = "BigWig", sample = "RNA"),
get_coverage(rnaseq_bw_rev, format = "BigWig", sample = "RNA"),
)
# turn sample names into factor for same order of tracks
df_coverage_bw <- df_coverage_bw %>%
mutate(sample_strand = factor(sample_strand, unique(sample_strand)))
# print
head(df_coverage_bw)
Plot tracks
Plot tracks
- plot individual coverage tracks
- first all tracks together
track1 <- track_coverage_combined(
df = df_coverage,
start_coord = 0,
end_coord = 5000,
track_color = c("#B3B3B3", "#C9C9C9", "#7570B3", "#9C97DA")
)
track1

- then all tracks separated by
sample
and
strand
track2 <- track_coverage_separate(
df = df_coverage,
start_coord = 0,
end_coord = 5000,
track_color = c("#B3B3B3", "#C9C9C9", "#7570B3", "#9C97DA")
)
track2

- all tracks can be customized by changing the
theme
options
track3 <- track_coverage_separate(
df = df_coverage,
start_coord = 0,
end_coord = 5000,
track_color = c("#B3B3B3", "#C9C9C9", "#7570B3", "#9C97DA")
) +
theme_bw() +
theme(
panel.spacing.y = unit(-0.2, "pt"),
axis.text.y = element_blank()
)
track3

- test if coverage plots for Bam and BigWig files are identical
- they are (left: Bam, right: BigWig)
cowplot::plot_grid(
track_coverage_separate(
df = filter(df_coverage, sample == "RNA"),
start_coord = 0,
end_coord = 5000,
track_color = c("#B3B3B3", "#C9C9C9")
) + theme_bw(),
track_coverage_separate(
df = filter(df_coverage_bw, sample == "RNA"),
start_coord = 0,
end_coord = 5000,
track_color = c("#B3B3B3", "#C9C9C9")
) + theme_bw(),
ncol = 2, align = "h"
)

Plot genome features
- plot genomic features such as CDSs or transcripts
- use a prettier theme right from the start
track4 <- track_genomic_features(
data = as_tibble(list_cds) %>%
filter(seqnames == unique(df_coverage$seqnames)), ,
name_id = "tx_name",
track_name = "GENES",
start_coord = 0,
end_coord = 5000,
track_color = grey(0.4)
) +
theme_bw() +
theme(
legend.position = "none",
axis.text.y = element_blank()
)
track4

Plot coverage with size reduction
- sometimes coverage of large genonomic ranges can be too dense
- file size gets large and plotting becomes slow
- in this case
get_coverage
can be used with
binnin = TRUE
- data will be binned in
bin_width
-wide windows, and
coverage calculated by function bin_fun
- default is to use 100 nt windows and
mean()
for
averaging
df_coverage_binned <- get_coverage(rnaseq, format = "bam", sample = "RNA", binning = TRUE)
# turn sample names into factor for same order of tracks
df_coverage_binned <- df_coverage_binned %>%
mutate(sample_strand = factor(sample_strand, unique(sample_strand)))
cowplot::plot_grid(
track_coverage_separate(
df = filter(df_coverage, sample == "RNA"),
start_coord = 0,
end_coord = 5000,
track_color = c("#B3B3B3", "#C9C9C9")
) + theme_bw(),
track_coverage_separate(
df = filter(df_coverage_binned, sample == "RNA"),
start_coord = 0,
end_coord = 5000,
track_color = c("#B3B3B3", "#C9C9C9")
) + theme_bw(),
ncol = 2, align = "h"
)

LS0tCnRpdGxlOiAiUGxvdCBjb3ZlcmFnZSB0cmFja3MgZm9yIGdlbm9taWMgZGF0YSIKYXV0aG9yOiBNaWNoYWVsIEphaG4KZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIsICVZJylgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRoZW1lOiBjb3NtbwogICAgdG9jOiBubwogICAgbnVtYmVyX3NlY3Rpb25zOiBubwogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IG5vCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCiMjIEJhY2tncm91bmQKClRoZSBmdW5jdGlvbnMgYW5kIGNvZGUgY2h1bmtzIGNvbGxlY3RlZCBoZXJlIGZ1bGxmaWxsIGEgdmVyeSBzaW1wbGUgcHVycG9zZTogCnRvIHBsb3QgcmVhZCBjb3ZlcmFnZSBvciByZWxhdGVkIGluZm9ybWF0aW9uLCBhcyB3ZWxsIGFzIGdlbmUgYW5ub3RhdGlvbiBhcwpjb3ZlcmF0ZSB0cmFja3Mgb24gb25lIGNhbnZhcy4gVGhpcyBpcyBvZnRlbiB2ZXJ5IHVzZWZ1bCBmb3Igc2VxdWVuY2luZyBkYXRhIHRoYXQKaXMgY3JlYXRlZCBmcm9tIGxhcmdlIHRocm91Z2hwdXQgcGlwZWxpbmVzLCBhbmQgd2hlcmUgaXQgaXMgbm90IHByYWN0aWNhbCB0bwppbXBvcnQgYW5kIHBsb3QgY292ZXJhZ2UgZXRjIGluIGEgZ2Vub21lIGJyb3dzZXIgc3VjaCBhcyBJR1YuCgojIyBMaWJyYXJpZXMgYW5kIHRlc3QgZGF0YQoKIyMjIFBhY2thZ2VzCgotIGBnZ2NvdmVyYWdlYCBjYW4gYmUgaW5zdGFsbGVkIHdpdGggbGF0ZXN0IHZlcnNpb24gZnJvbSBnaXRodWIKCmBgYHtyLCBldmFsID0gRkFMU0V9CmRldnRvb2xzOjppbnN0YWxsX2dpdGh1Yigic2hvd3RlZXRoL2dnY292ZXJhZ2UiKQpgYGAKCi0gbG9hZCByZXF1aXJlZCBsaWJyYXJpZXMKCmBgYHtyfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkodGlkeXZlcnNlKQogIGxpYnJhcnkoR2Vub21pY0ZlYXR1cmVzKQogIGxpYnJhcnkoR2Vub21pY1JhbmdlcykKICBsaWJyYXJ5KEdlbm9taWNBbGlnbm1lbnRzKQogIGxpYnJhcnkoZ2djb3ZlcmFnZSkKICBsaWJyYXJ5KGdncmVwZWwpCiAgbGlicmFyeShjb3dwbG90KQp9KQpgYGAKCiMjIyBJbXBvcnQgdXRpbGl0eSBmdW5jdGlvbnMKCi0gYGdldF9jb3ZlcmFnZWAgY2FsY3VsYXRlcyBjb3ZlcmFnZSBwZXIgbnQgKHNpbWlsYXIgdG8gYmVkZ3JhcGggZmlsZXMpIGZyb20gYEdBbGlnbm1lbnRzYCBvciBgR3Jhbmdlc2AKLSBgdHJhY2tfY292ZXJhZ2VfY29tYmluZWRgIHBsb3RzIG9uZSBmaWd1cmUgd2l0aCBkaWZmZXJlbnQgdHJhY2tzIG92ZXJsYWlkCi0gYHRyYWNrX2NvdmVyYWdlX3NlcGFyYXRlYCBwbG90cyBvbmUgZmlndXJlIHdpdGggZGlmZmVyZW50IHRyYWNrcyBzdGFja2VkIG9uIGVhY2ggb3RoZXIKLSBgdHJhY2tfZ2Vub21pY19mZWF0dXJlc2AgcGxvdHMgZ2Vub21lIGZlYXR1cmVzIGZyb20gYSBgR3Jhbmdlc2Agb3IgYEdyYW5nZXNMaXN0YAotIGBjaGVja19nZmZgIGNoZWNrcyBgKi5nZmZgIGZpbGVzIGZvciBwcm9ibGVtYXRpYyB0YWdzIGFuZCByZXBsYWNlcyB0aG9zZQoKCmBgYHtyfQpzb3VyY2UoIi4uL3NvdXJjZS9jb3ZlcmFnZV91dGlscy5SIikKYGBgCgojIyMgSW1wb3J0IGdlbm9tZSBhbm5vdGF0aW9uCgotIGZpcnN0IGltcG9ydCB0aGUgYCouZ2ZmYCBmaWxlIHdpdGggZ2VuZSBhbm5vdGF0aW9uCi0gaGFzIHRvIGJlIHBhcnNlZCBieSBgR2Vub21pY0ZlYXR1cmVzYCBvciBgT1JGaWtgIHBhY2thZ2VzIHRvIHR4ZGIKLSB0aGVuIENEUywgdHJhbnNjcmlwdHMgZXRjLiBjYW4gYmUgZXh0cmFjdGVkIGFzIGBHUmFuZ2VzYCBvYmplY3QKCgpgYGB7cn0KIyBmaWxlIHBhdGgKZ2Vub21lX2dmZiA8LSAiLi4vZGF0YS9zcHlvZ2VuZXNfZ2Vub21lLmdmZiIKCiMgY2hlY2sgZ2ZmIGZpbGUKY2hlY2tfZ2ZmKGdlbm9tZV9nZmYsICJOYW1lPVthLXpBLVowLTlcXHNcXC1cXF9dKiwiLCAiTmFtZT1bYS16QS1aMC05XFxzXFwtXFxfXSoiKQoKIyBpbXBvcnQgZ2Vub21lIGFubm90YXRpb24KdHhkYiA8LSBtYWtlVHhEYkZyb21HRkYoZ2Vub21lX2dmZiwgZm9ybWF0ID0gImdmZjMiKQoKIyBleHRyYWN0IGFsbCBDRFNzIGFzIEdSYW5nZXMgb2JqZWN0Cmxpc3RfY2RzIDwtIHRyYW5zY3JpcHRzKHR4ZGIpCmhlYWQobGlzdF9jZHMpCmBgYAoKIyMjIEltcG9ydCAqLmJhbSBmaWxlcwoKLSBuZXh0IGltcG9ydCBleGFtcGxlIGAqLmJhbWAgZmlsZXMgd2l0aCByZWFkIGRhdGEKLSBvbmUgUk5BU2VxIGRhdGEgZmlsZSwgZmlyc3QgNTAwMCBudCBvZiBnZW5vbWUKLSBvbmUgUmlib1NlcSBkYXRhIGZpbGUsIGZpcnN0IDUwMDAgbnQgb2YgZ2Vub21lCgpgYGB7cn0Kcm5hc2VxIDwtIHJlYWRHQWxpZ25tZW50cygiLi4vZGF0YS9zcHlvZ2VuZXNfcm5hc2VxLmJhbSIpCnJpYm9zZXEgPC0gcmVhZEdBbGlnbm1lbnRzKCIuLi9kYXRhL3NweW9nZW5lc19yaWJvc2VxLmJhbSIpCgpoZWFkKHJuYXNlcSkKYGBgCgotIGlmIHJlYWQgZGF0YSBpcyAqKnBhaXJlZC1lbmQqKiBpbnN0ZWFkIG9mIHNpbmdsZS1lbmQsIHVzZSBgcmVhZEdBbGlnbm1lbnRzUGFpcnNgCi0gbm90ZTogZG9lcyBub3Qgd29yayB3aXRoIHRoaXMgZXhhbXBsZSBkYXRhCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpybmFzZXEgPC0gcmVhZEdBbGlnbm1lbnRQYWlycygiLi4vZGF0YS9zcHlvZ2VuZXNfcm5hc2VxLmJhbSIpCnJpYm9zZXEgPC0gcmVhZEdBbGlnbm1lbnRQYWlycygiLi4vZGF0YS9zcHlvZ2VuZXNfcmlib3NlcS5iYW0iKQpgYGAKCiMjIyBHZXQgY292ZXJhZ2UgZm9yICouYmFtIGZpbGVzCgotIHNwZWNpZnkgdGhlIGFwcHJvcHJpYXRlIGRhdGEgc291cmNlIHdpdGggYGZvcm1hdGAgKG9uZSBvZiAiYmFtIiwgIkJpZ1dpZyIpCi0gc3BlY2lmeSBhIG5hbWUgZm9yIHRoZSB0cmFjayB3aXRoIGBzYW1wbGVgCi0gY292ZXJhZ2UgaXMgY2FsY3VsYXRlZCBwZXIgYHN0cmFuZGAKLSBhIG5ldyB2YXJpYWJsZSBgc2FtcGxlX3N0cmFuZGAgY29tYmluZXMgdGhlIHR3bwoKYGBge3J9CmRmX2NvdmVyYWdlIDwtIGJpbmRfcm93cygKICBnZXRfY292ZXJhZ2Uocm5hc2VxLCBmb3JtYXQgPSAiYmFtIiwgc2FtcGxlID0gIlJOQSIpLAogIGdldF9jb3ZlcmFnZShyaWJvc2VxLCBmb3JtYXQgPSAiYmFtIiwgc2FtcGxlID0gIlJJQk8iKQopCgojIHR1cm4gc2FtcGxlIG5hbWVzIGludG8gZmFjdG9yIGZvciBzYW1lIG9yZGVyIG9mIHRyYWNrcwpkZl9jb3ZlcmFnZSA8LSBkZl9jb3ZlcmFnZSAlPiUKICBtdXRhdGUoc2FtcGxlX3N0cmFuZCA9IGZhY3RvcihzYW1wbGVfc3RyYW5kLCB1bmlxdWUoc2FtcGxlX3N0cmFuZCkpKQoKIyBwcmludApoZWFkKGRmX2NvdmVyYWdlKQpgYGAKCiMjIyBJbXBvcnQgKi5iaWd3aWcgZmlsZXMKCi0gaW1wb3J0IGAqLmJpZ3dpZ2AvYCouYndgIGFzIGFuIGFsdGVybmF0aXZlIHRvIGJhbQotIGJpZ3dpZyBmaWxlcyBhcmUgc3RyYW5kLXNwZWNpZmljIGJpbmFyeSAoY29tcHJlc3NlZCkgY292ZXJhZ2UgZmlsZXMKLSB0aGVyZWZvcmUgbmVlZCB0d28gZmlsZXMgcGVyIHNhbXBsZSwgYW5kIHNwZWNpZnkgc3RyYW5kIHdoZW4gaW1wb3J0aW5nCi0gc2ltaWxhciB0byBgKi5iZWRncmFwaGAgZmlsZXMKCmBgYHtyfQpybmFzZXFfYndfZm9yIDwtIHJ0cmFja2xheWVyOjppbXBvcnQoY29uID0gIi4uL2RhdGEvc3B5b2dlbmVzX3JuYXNlcV9mb3J3YXJkLmJ3IiwgZm9ybWF0ID0gIkJpZ1dpZyIpCnJuYXNlcV9id19yZXYgPC0gcnRyYWNrbGF5ZXI6OmltcG9ydChjb24gPSAiLi4vZGF0YS9zcHlvZ2VuZXNfcm5hc2VxX3JldmVyc2UuYnciLCBmb3JtYXQgPSAiQmlnV2lnIikKCnN0cmFuZChybmFzZXFfYndfZm9yKSA8LSAiKyIKc3RyYW5kKHJuYXNlcV9id19yZXYpIDwtICItIgoKaGVhZChybmFzZXFfYndfZm9yKQpgYGAKCiMjIyBHZXQgY292ZXJhZ2UgZm9yICAqLmJpZ3dpZyBmaWxlcwoKYGBge3J9CmRmX2NvdmVyYWdlX2J3IDwtIGJpbmRfcm93cygKICBnZXRfY292ZXJhZ2Uocm5hc2VxX2J3X2ZvciwgZm9ybWF0ID0gIkJpZ1dpZyIsIHNhbXBsZSA9ICJSTkEiKSwKICBnZXRfY292ZXJhZ2Uocm5hc2VxX2J3X3JldiwgZm9ybWF0ID0gIkJpZ1dpZyIsIHNhbXBsZSA9ICJSTkEiKSwKKQoKIyB0dXJuIHNhbXBsZSBuYW1lcyBpbnRvIGZhY3RvciBmb3Igc2FtZSBvcmRlciBvZiB0cmFja3MKZGZfY292ZXJhZ2VfYncgPC0gZGZfY292ZXJhZ2VfYncgJT4lCiAgbXV0YXRlKHNhbXBsZV9zdHJhbmQgPSBmYWN0b3Ioc2FtcGxlX3N0cmFuZCwgdW5pcXVlKHNhbXBsZV9zdHJhbmQpKSkKCiMgcHJpbnQKaGVhZChkZl9jb3ZlcmFnZV9idykKYGBgCgojIyBQbG90IHRyYWNrcwoKIyMjIFBsb3QgdHJhY2tzCgotIHBsb3QgaW5kaXZpZHVhbCBjb3ZlcmFnZSB0cmFja3MKLSBmaXJzdCBhbGwgdHJhY2tzIHRvZ2V0aGVyCgpgYGB7ciwgZmlnLndpZHRoID0gNy41LCBmaWcuaGVpZ2h0ID0gMy41fQp0cmFjazEgPC0gdHJhY2tfY292ZXJhZ2VfY29tYmluZWQoCiAgZGYgPSBkZl9jb3ZlcmFnZSwKICBzdGFydF9jb29yZCA9IDAsCiAgZW5kX2Nvb3JkID0gNTAwMCwKICB0cmFja19jb2xvciA9IGMoIiNCM0IzQjMiLCAiI0M5QzlDOSIsICIjNzU3MEIzIiwgIiM5Qzk3REEiKQopCgp0cmFjazEKYGBgCgotIHRoZW4gYWxsIHRyYWNrcyBzZXBhcmF0ZWQgYnkgYHNhbXBsZWAgYW5kIGBzdHJhbmRgCgpgYGB7ciwgZmlnLndpZHRoID0gNy41LCBmaWcuaGVpZ2h0ID0gMy41fQp0cmFjazIgPC0gdHJhY2tfY292ZXJhZ2Vfc2VwYXJhdGUoCiAgZGYgPSBkZl9jb3ZlcmFnZSwKICBzdGFydF9jb29yZCA9IDAsCiAgZW5kX2Nvb3JkID0gNTAwMCwKICB0cmFja19jb2xvciA9IGMoIiNCM0IzQjMiLCAiI0M5QzlDOSIsICIjNzU3MEIzIiwgIiM5Qzk3REEiKQopCgp0cmFjazIKYGBgCgotIGFsbCB0cmFja3MgY2FuIGJlIGN1c3RvbWl6ZWQgYnkgY2hhbmdpbmcgdGhlIGB0aGVtZWAgb3B0aW9ucwoKYGBge3IsIGZpZy53aWR0aCA9IDcuNSwgZmlnLmhlaWdodCA9IDMuNX0KdHJhY2szIDwtIHRyYWNrX2NvdmVyYWdlX3NlcGFyYXRlKAogIGRmID0gZGZfY292ZXJhZ2UsCiAgc3RhcnRfY29vcmQgPSAwLAogIGVuZF9jb29yZCA9IDUwMDAsCiAgdHJhY2tfY29sb3IgPSBjKCIjQjNCM0IzIiwgIiNDOUM5QzkiLCAiIzc1NzBCMyIsICIjOUM5N0RBIikKKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoCiAgICBwYW5lbC5zcGFjaW5nLnkgPSB1bml0KC0wLjIsICJwdCIpLAogICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkKICApCgp0cmFjazMKYGBgCgotIHRlc3QgaWYgY292ZXJhZ2UgcGxvdHMgZm9yIEJhbSBhbmQgQmlnV2lnIGZpbGVzIGFyZSBpZGVudGljYWwKLSB0aGV5IGFyZSAobGVmdDogQmFtLCByaWdodDogQmlnV2lnKQoKYGBge3IsIGZpZy53aWR0aCA9IDcuNSwgZmlnLmhlaWdodCA9IDMuNX0KY293cGxvdDo6cGxvdF9ncmlkKAogIHRyYWNrX2NvdmVyYWdlX3NlcGFyYXRlKAogICAgZGYgPSBmaWx0ZXIoZGZfY292ZXJhZ2UsIHNhbXBsZSA9PSAiUk5BIiksCiAgICBzdGFydF9jb29yZCA9IDAsCiAgICBlbmRfY29vcmQgPSA1MDAwLAogICAgdHJhY2tfY29sb3IgPSBjKCIjQjNCM0IzIiwgIiNDOUM5QzkiKQogICkgKyB0aGVtZV9idygpLAogIHRyYWNrX2NvdmVyYWdlX3NlcGFyYXRlKAogICAgZGYgPSBmaWx0ZXIoZGZfY292ZXJhZ2VfYncsIHNhbXBsZSA9PSAiUk5BIiksCiAgICBzdGFydF9jb29yZCA9IDAsCiAgICBlbmRfY29vcmQgPSA1MDAwLAogICAgdHJhY2tfY29sb3IgPSBjKCIjQjNCM0IzIiwgIiNDOUM5QzkiKQogICkgKyB0aGVtZV9idygpLAogIG5jb2wgPSAyLCBhbGlnbiA9ICJoIgopCmBgYAoKCiMjIyBQbG90IGdlbm9tZSBmZWF0dXJlcwoKLSBwbG90IGdlbm9taWMgZmVhdHVyZXMgc3VjaCBhcyBDRFNzIG9yIHRyYW5zY3JpcHRzCi0gdXNlIGEgcHJldHRpZXIgdGhlbWUgcmlnaHQgZnJvbSB0aGUgc3RhcnQKCmBgYHtyLCBmaWcud2lkdGggPSA3LjUsIGZpZy5oZWlnaHQgPSAxLjJ9CnRyYWNrNCA8LSB0cmFja19nZW5vbWljX2ZlYXR1cmVzKAogIGRhdGEgPSBhc190aWJibGUobGlzdF9jZHMpICU+JQogICAgZmlsdGVyKHNlcW5hbWVzID09IHVuaXF1ZShkZl9jb3ZlcmFnZSRzZXFuYW1lcykpLCAsCiAgbmFtZV9pZCA9ICJ0eF9uYW1lIiwKICB0cmFja19uYW1lID0gIkdFTkVTIiwKICBzdGFydF9jb29yZCA9IDAsCiAgZW5kX2Nvb3JkID0gNTAwMCwKICB0cmFja19jb2xvciA9IGdyZXkoMC40KQopICsKICB0aGVtZV9idygpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpCiAgKQoKdHJhY2s0CmBgYAoKIyMjIENvbWJpbmUgZGlmZmVyZW50IHRyYWNrcyBpbiBvbmUgZmlndXJlCgotIGNhbiB1c2UgYGdncHVicjo6Z2dhcnJhbmdlYCBvciBgY293cGxvdDo6cGxvdF9ncmlkYCB0byBhcnJhbmdlIG11bHRpcGxlIHBsb3RzCgpgYGB7cn0KY293cGxvdDo6cGxvdF9ncmlkKAogIHRyYWNrMyArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDEyLCAxMiwgMCwgMTIpLCAicG9pbnQiKSksCiAgdHJhY2s0ICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMCwgMTIsIDEyLCAxMiksICJwb2ludCIpKSwKICBucm93ID0gMiwgcmVsX2hlaWdodHMgPSBjKDAuNywgMC4zKSwgYWxpZ24gPSAidiIKKQpgYGAKCgojIyMgUGxvdCBjb3ZlcmFnZSB3aXRoIHNpemUgcmVkdWN0aW9uCgotIHNvbWV0aW1lcyBjb3ZlcmFnZSBvZiBsYXJnZSBnZW5vbm9taWMgcmFuZ2VzIGNhbiBiZSB0b28gZGVuc2UKLSBmaWxlIHNpemUgZ2V0cyBsYXJnZSBhbmQgcGxvdHRpbmcgYmVjb21lcyBzbG93Ci0gaW4gdGhpcyBjYXNlIGBnZXRfY292ZXJhZ2VgIGNhbiBiZSB1c2VkIHdpdGggYGJpbm5pbiA9IFRSVUVgCi0gZGF0YSB3aWxsIGJlIGJpbm5lZCBpbiBgYmluX3dpZHRoYC13aWRlIHdpbmRvd3MsIGFuZCBjb3ZlcmFnZSBjYWxjdWxhdGVkIGJ5IGZ1bmN0aW9uIGBiaW5fZnVuYAotIGRlZmF1bHQgaXMgdG8gdXNlIDEwMCBudCB3aW5kb3dzIGFuZCBgbWVhbigpYCBmb3IgYXZlcmFnaW5nCgpgYGB7ciwgZmlnLndpZHRoID0gNy41LCBmaWcuaGVpZ2h0ID0gMy41fQpkZl9jb3ZlcmFnZV9iaW5uZWQgPC0gZ2V0X2NvdmVyYWdlKHJuYXNlcSwgZm9ybWF0ID0gImJhbSIsIHNhbXBsZSA9ICJSTkEiLCBiaW5uaW5nID0gVFJVRSkKCiMgdHVybiBzYW1wbGUgbmFtZXMgaW50byBmYWN0b3IgZm9yIHNhbWUgb3JkZXIgb2YgdHJhY2tzCmRmX2NvdmVyYWdlX2Jpbm5lZCA8LSBkZl9jb3ZlcmFnZV9iaW5uZWQgJT4lCiAgbXV0YXRlKHNhbXBsZV9zdHJhbmQgPSBmYWN0b3Ioc2FtcGxlX3N0cmFuZCwgdW5pcXVlKHNhbXBsZV9zdHJhbmQpKSkKCmNvd3Bsb3Q6OnBsb3RfZ3JpZCgKICB0cmFja19jb3ZlcmFnZV9zZXBhcmF0ZSgKICAgIGRmID0gZmlsdGVyKGRmX2NvdmVyYWdlLCBzYW1wbGUgPT0gIlJOQSIpLAogICAgc3RhcnRfY29vcmQgPSAwLAogICAgZW5kX2Nvb3JkID0gNTAwMCwKICAgIHRyYWNrX2NvbG9yID0gYygiI0IzQjNCMyIsICIjQzlDOUM5IikKICApICsgdGhlbWVfYncoKSwKICB0cmFja19jb3ZlcmFnZV9zZXBhcmF0ZSgKICAgIGRmID0gZmlsdGVyKGRmX2NvdmVyYWdlX2Jpbm5lZCwgc2FtcGxlID09ICJSTkEiKSwKICAgIHN0YXJ0X2Nvb3JkID0gMCwKICAgIGVuZF9jb29yZCA9IDUwMDAsCiAgICB0cmFja19jb2xvciA9IGMoIiNCM0IzQjMiLCAiI0M5QzlDOSIpCiAgKSArIHRoZW1lX2J3KCksCiAgbmNvbCA9IDIsIGFsaWduID0gImgiCikKYGBgCg==