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")
  • load required libraries
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

Combine different tracks in one figure

  • can use ggpubr::ggarrange or cowplot::plot_grid to arrange multiple plots
cowplot::plot_grid(
  track3 + theme(plot.margin = unit(c(12, 12, 0, 12), "point")),
  track4 + theme(plot.margin = unit(c(0, 12, 12, 12), "point")),
  nrow = 2, rel_heights = c(0.7, 0.3), align = "v"
)

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==