suppressPackageStartupMessages({
  library(VariantAnnotation)
  library(tidyverse)
  library(RColorBrewer)
  library(ggrepel)
  library(cowplot)
})

Load and process VCF files

First, we need to load the VCF files using readVcf function from VariantAnnotation package. We load both classic GATK and Parabricks GATK VCFs as objects into a named list.

vcf.filenames <- c("classic GATK" = "~/WES_data/vcf_analysis/WES_tumor.annotated.vcf",
                   "Parabricks GATK" = "~/WES_data/vcf_analysis/PB_WES_tumor.annotated.vcf")

vcf.list <- lapply(vcf.filenames, readVcf, genome = "hg38")

Here’s what looking at dimensions of each element of this list gives us:

lapply(vcf.list, dim)
$`classic GATK`
[1] 7480    2

$`Parabricks GATK`
[1] 8994    2

First value is the number of mutations and the second is the number of samples (normal and tumor). That is a lot of variants, luckly, we applied a Mutect2 filter. Let’s subset the VCF objects leaving only variants which passed the filter:

vcf.list.qc <-
  lapply(vcf.list, function(x)
    x[rowRanges(x)$FILTER == "PASS"])

# Also, filter only variants mapped correctly to a chromosome:
vcf.list.qc <-
  lapply(vcf.list.qc, function(x)
    x[!(grepl(names(x), pattern = "chrUn_")),])

lapply(vcf.list.qc, dim)
$`classic GATK`
[1] 2603    2

$`Parabricks GATK`
[1] 3146    2

Next block of code will extract only the relevant fields and columns from the VCF objects, creating a data frame where each row is a detected variant. It will include positional information and annotations of interest (gene name, type of SNV, allelic frequence) along with additional filter information which we will use to further prune the variants.

extractFUNCOTATION <-
  function(vcf,
           fields = c(
             "Gencode_34_hugoSymbol",
             "Gencode_34_variantClassification",
             "Gencode_34_secondaryVariantClassification",
             "Gencode_34_variantType"
           )) {
    annotation_colnames <-
      info(vcf@metadata$header)["FUNCOTATION", "Description"] %>%
      stringr::str_remove("Functional annotation from the Funcotator tool.  Funcotation fields are: ") %>%
      stringr::str_split(pattern = "\\|") %>%
      unlist()
    
    as.data.frame(info(vcf)[["FUNCOTATION"]]) %>%
      dplyr::select("FUNCOTATION" = value) %>%
      dplyr::mutate(
        FUNCOTATION = word(FUNCOTATION, 1, sep = "\\]"),
        FUNCOTATION = str_remove_all(FUNCOTATION, "[\\[\\]]")
      ) %>%
      tidyr::separate(FUNCOTATION,
                      into = annotation_colnames,
                      sep = "\\|",
                      fill = "right") %>%
      dplyr::select(all_of(fields))
  }

vcf.list.anno <- lapply(vcf.list.qc, function(x) extractFUNCOTATION(x))

snv.list <- list()
for(sample.name in names(vcf.list.qc)) {
  vcf <- vcf.list.qc[[sample.name]]
  df.anno <- vcf.list.anno[[sample.name]]
  snv.list[[sample.name]] <- as.data.frame(geno(vcf)[["AF"]]) %>%
    
    # since the sample is the same in both VCF, we rename the AF columns equally
    setNames(., c("AF_CTRL", "AF_TUMOR")) %>%
    
    mutate_all( ~ unlist(.)) %>%
    cbind(., df.anno) %>%
    mutate(
      DP = info(vcf)[["DP"]],
      TLOD = info(vcf)[["TLOD"]] %>% unlist()
    ) %>%
    rownames_to_column("Variant") %>%
    mutate(Position_hg38 = word(Variant, 1, sep = "_"),
           .before = "Variant") %>%
    mutate(Variant = word(Variant, 2, sep = "_"))
}

lapply(snv.list, head, 10) # Print only first 10 rows
$`classic GATK`

$`Parabricks GATK`
NA

Variant filters

Most of these SNVs are artefacts that we will need to filter out.

First, we can remove non-coding variants: introns, variants from intergenic regions, etc. Next block of code creates a second_filter column in the data frame with SNVs which will help us filter the mutations without losing possible false negatives.

noncoding.vars <- c("IGR", "INTRON", "COULD_NOT_DETERMINE", "RNA", "SILENT")

snv.list <- snv.list %>%
  lapply(
  mutate,
    second_filter = case_when(
      Gencode_34_variantClassification %in% noncoding.vars ~ "non-coding",
      Gencode_34_secondaryVariantClassification %in% noncoding.vars ~ "non-coding",
      TRUE ~ "PASS"
    )
  )

lapply(snv.list, filter, second_filter == "PASS") %>% 
  lapply(dim)
$`classic GATK`
[1] 1195   11

$`Parabricks GATK`
[1] 1380   11

Next, we can filter the mutations by thresholding them by tumor allele frequency > 10% and TLOD > 15. TLOD stands for tumor log odds and is a of probability that the variant is present in the tumor sample relative to the expected noise.

AF_TUMOR_thresh <- 0.1
TLOD_thresh <- 15

snv.list <- snv.list %>%
  lapply(
    mutate,
    second_filter = case_when(
      second_filter == "PASS" & TLOD < TLOD_thresh ~ "low_TLOD",
      second_filter == "PASS" &
        AF_TUMOR < AF_TUMOR_thresh ~ "low_TUMOR_AF",
      TRUE ~ second_filter
    )
  )

lapply(snv.list, filter, second_filter == "PASS") %>% 
  lapply(dim)
$`classic GATK`
[1] 871  11

$`Parabricks GATK`
[1] 1064   11

Scatterplot of

max.TLOD <- bind_rows(snv.list) %>%
  select(TLOD) %>% max()
min.TLOD <- bind_rows(snv.list) %>%
  select(TLOD) %>% min()

lapply(names(snv.list),
       function(name) {
         snv.list[[name]] %>%
           filter(second_filter != "non-coding") %>%
           ggplot(aes(y = AF_TUMOR, x = TLOD)) +
           theme_bw() +
           labs(title = name) +
           geom_point(
             aes(color = Gencode_34_variantClassification),
             shape = 1,
             size = 2,
             stroke = 1
           ) +
           scale_y_continuous(labels = scales::percent, limits = c(0, 1)) +
           scale_x_continuous(
             trans = "log10",
             limits = c(min.TLOD, max.TLOD),
             breaks = c(1, 10, 20, 50, 100, 200)
           ) +
           geom_vline(xintercept = TLOD_thresh, linetype = 2) +
           geom_hline(yintercept = AF_TUMOR_thresh, linetype = 2)
       })
[[1]]

[[2]]

Compare the allele frequencies for SNV detected in both samples

Let’s combine the snv.list into one data frame and inspect the overlap of filtered mutations:

combined.snv.df <- snv.list %>% 
  bind_rows(.id = "pipeline") %>% 
  filter(second_filter == "PASS") %>% 
  dplyr::select(pipeline, Position_hg38, Variant, AF_TUMOR,
         Gencode_34_hugoSymbol, Gencode_34_variantClassification) %>% 
  pivot_wider(names_from = "pipeline", values_from = "AF_TUMOR")

combined.snv.df %>% 
  mutate(detection = case_when(
    !is.na(`classic GATK`) & !is.na(`Parabricks GATK`) ~ "classic and Parabricks",
    !is.na(`classic GATK`) & is.na(`Parabricks GATK`) ~ "classic",
    is.na(`classic GATK`) & !is.na(`Parabricks GATK`) ~ "Parabricks"
  )) %>% 
  group_by(detection, Gencode_34_variantClassification) %>% 
  summarise(N = n()) %>% 
  pivot_wider(names_from = detection, values_from = N)
`summarise()` has grouped output by 'detection'. You can override using the `.groups` argument.

One of the most commonly mutated genes in tumors is TP53. Using filter by gene name we can check the presence of TP53 mutations.

Finally, lets compare the allele frequencies for the variants detected by both pipelines:

combined.snv.df %>%
  ggplot(aes(x = `classic GATK`, y = `Parabricks GATK`)) +
  geom_point(aes(fill = Gencode_34_variantClassification),
             shape = 21,
             size = 2) +
  theme_bw() +
  scale_y_continuous(labels = scales::percent, limits = c(0, 1)) +
  scale_x_continuous(labels = scales::percent, limits = c(0, 1))

Session info

sessionInfo()
R version 4.3.1 (2023-06-16)
Platform: x86_64-conda-linux-gnu (64-bit)
Running under: Ubuntu 22.04.3 LTS

Matrix products: default
BLAS/LAPACK: /home/bench-user/.apps/conda/lib/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

time zone: Etc/UTC
tzcode source: system (glibc)

attached base packages:
[1] stats4    stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] cowplot_1.1.3               ggrepel_0.9.5               RColorBrewer_1.1-3          lubridate_1.9.3             forcats_1.0.0               stringr_1.5.1              
 [7] dplyr_1.1.4                 purrr_1.0.2                 readr_2.1.5                 tidyr_1.3.1                 tibble_3.2.1                ggplot2_3.4.4              
[13] tidyverse_2.0.0             VariantAnnotation_1.48.1    Rsamtools_2.18.0            Biostrings_2.70.1           XVector_0.42.0              SummarizedExperiment_1.32.0
[19] Biobase_2.62.0              GenomicRanges_1.54.1        GenomeInfoDb_1.38.1         IRanges_2.36.0              S4Vectors_0.40.2            MatrixGenerics_1.14.0      
[25] matrixStats_1.2.0           BiocGenerics_0.48.1        

loaded via a namespace (and not attached):
 [1] DBI_1.2.1                bitops_1.0-7             biomaRt_2.58.0           rlang_1.1.3              magrittr_2.0.3           compiler_4.3.1           RSQLite_2.3.4           
 [8] GenomicFeatures_1.54.1   png_0.1-8                vctrs_0.6.5              pkgconfig_2.0.3          crayon_1.5.2             fastmap_1.1.1            dbplyr_2.4.0            
[15] labeling_0.4.3           utf8_1.2.4               rmarkdown_2.25           tzdb_0.4.0               ggbeeswarm_0.7.2         bit_4.0.5                xfun_0.41               
[22] zlibbioc_1.48.0          cachem_1.0.8             jsonlite_1.8.8           progress_1.2.3           blob_1.2.4               DelayedArray_0.28.0      BiocParallel_1.36.0     
[29] parallel_4.3.1           prettyunits_1.2.0        R6_2.5.1                 bslib_0.6.1              stringi_1.8.3            rtracklayer_1.62.0       jquerylib_0.1.4         
[36] Rcpp_1.0.12              knitr_1.45               timechange_0.3.0         Matrix_1.6-5             tidyselect_1.2.0         rstudioapi_0.15.0        abind_1.4-5             
[43] yaml_2.3.8               codetools_0.2-19         curl_5.1.0               lattice_0.22-5           withr_3.0.0              KEGGREST_1.42.0          evaluate_0.23           
[50] BiocFileCache_2.10.1     xml2_1.3.6               pillar_1.9.0             filelock_1.0.3           rsconnect_1.2.0          generics_0.1.3           RCurl_1.98-1.14         
[57] hms_1.1.3                munsell_0.5.0            scales_1.3.0             glue_1.7.0               tools_4.3.1              BiocIO_1.12.0            BSgenome_1.70.1         
[64] GenomicAlignments_1.38.0 XML_3.99-0.16.1          grid_4.3.1               AnnotationDbi_1.64.1     colorspace_2.1-0         GenomeInfoDbData_1.2.11  beeswarm_0.4.0          
[71] restfulr_0.0.15          vipor_0.4.7              cli_3.6.2                rappdirs_0.3.3           fansi_1.0.6              S4Arrays_1.2.0           gtable_0.3.4            
[78] sass_0.4.8               digest_0.6.34            SparseArray_1.2.2        farver_2.1.1             rjson_0.2.21             memoise_2.0.1            htmltools_0.5.7         
[85] lifecycle_1.0.4          httr_1.4.7               bit64_4.0.5             
LS0tCnRpdGxlOiAiUUMgYW5kIGZpbHRlcmluZyBvZiBWQ0YgZmlsZXMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyIHNldHVwfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkoVmFyaWFudEFubm90YXRpb24pCiAgbGlicmFyeSh0aWR5dmVyc2UpCiAgbGlicmFyeShSQ29sb3JCcmV3ZXIpCiAgbGlicmFyeShnZ3JlcGVsKQogIGxpYnJhcnkoY293cGxvdCkKfSkKYGBgCgojIExvYWQgYW5kIHByb2Nlc3MgVkNGIGZpbGVzCgpGaXJzdCwgd2UgbmVlZCB0byBsb2FkIHRoZSBWQ0YgZmlsZXMgdXNpbmcgYHJlYWRWY2ZgIGZ1bmN0aW9uIGZyb20gYFZhcmlhbnRBbm5vdGF0aW9uYCBwYWNrYWdlLiBXZSBsb2FkIGJvdGggY2xhc3NpYyBHQVRLIGFuZCBQYXJhYnJpY2tzIEdBVEsgVkNGcyBhcyBvYmplY3RzIGludG8gYSBuYW1lZCBsaXN0LgoKYGBge3Igd2FybmluZz1GQUxTRX0KdmNmLmZpbGVuYW1lcyA8LSBjKCJjbGFzc2ljIEdBVEsiID0gIn4vV0VTX2RhdGEvdmNmX2FuYWx5c2lzL1dFU190dW1vci5hbm5vdGF0ZWQudmNmIiwKICAgICAgICAgICAgICAgICAgICJQYXJhYnJpY2tzIEdBVEsiID0gIn4vV0VTX2RhdGEvdmNmX2FuYWx5c2lzL1BCX1dFU190dW1vci5hbm5vdGF0ZWQudmNmIikKCnZjZi5saXN0IDwtIGxhcHBseSh2Y2YuZmlsZW5hbWVzLCByZWFkVmNmLCBnZW5vbWUgPSAiaGczOCIpCmBgYAoKSGVyZSdzIHdoYXQgbG9va2luZyBhdCBkaW1lbnNpb25zIG9mIGVhY2ggZWxlbWVudCBvZiB0aGlzIGxpc3QgZ2l2ZXMgdXM6CgpgYGB7ciB3YXJuaW5nPUZBTFNFfQpsYXBwbHkodmNmLmxpc3QsIGRpbSkKYGBgCgpGaXJzdCB2YWx1ZSBpcyB0aGUgbnVtYmVyIG9mIG11dGF0aW9ucyBhbmQgdGhlIHNlY29uZCBpcyB0aGUgbnVtYmVyIG9mIHNhbXBsZXMgKG5vcm1hbCBhbmQgdHVtb3IpLiBUaGF0IGlzIGEgbG90IG9mIHZhcmlhbnRzLCBsdWNrbHksIHdlIGFwcGxpZWQgYSBNdXRlY3QyIGZpbHRlci4gTGV0J3Mgc3Vic2V0IHRoZSBWQ0Ygb2JqZWN0cyBsZWF2aW5nIG9ubHkgdmFyaWFudHMgd2hpY2ggcGFzc2VkIHRoZSBmaWx0ZXI6CgpgYGB7cn0KdmNmLmxpc3QucWMgPC0KICBsYXBwbHkodmNmLmxpc3QsIGZ1bmN0aW9uKHgpCiAgICB4W3Jvd1Jhbmdlcyh4KSRGSUxURVIgPT0gIlBBU1MiXSkKCiMgQWxzbywgZmlsdGVyIG9ubHkgdmFyaWFudHMgbWFwcGVkIGNvcnJlY3RseSB0byBhIGNocm9tb3NvbWU6CnZjZi5saXN0LnFjIDwtCiAgbGFwcGx5KHZjZi5saXN0LnFjLCBmdW5jdGlvbih4KQogICAgeFshKGdyZXBsKG5hbWVzKHgpLCBwYXR0ZXJuID0gImNoclVuXyIpKSxdKQoKbGFwcGx5KHZjZi5saXN0LnFjLCBkaW0pCmBgYAoKTmV4dCBibG9jayBvZiBjb2RlIHdpbGwgZXh0cmFjdCBvbmx5IHRoZSByZWxldmFudCBmaWVsZHMgYW5kIGNvbHVtbnMgZnJvbSB0aGUgVkNGIG9iamVjdHMsIGNyZWF0aW5nIGEgZGF0YSBmcmFtZSB3aGVyZSBlYWNoIHJvdyBpcyBhIGRldGVjdGVkIHZhcmlhbnQuIEl0IHdpbGwgaW5jbHVkZSBwb3NpdGlvbmFsIGluZm9ybWF0aW9uIGFuZCBhbm5vdGF0aW9ucyBvZiBpbnRlcmVzdCAoZ2VuZSBuYW1lLCB0eXBlIG9mIFNOViwgYWxsZWxpYyBmcmVxdWVuY2UpIGFsb25nIHdpdGggYWRkaXRpb25hbCBmaWx0ZXIgaW5mb3JtYXRpb24gd2hpY2ggd2Ugd2lsbCB1c2UgdG8gZnVydGhlciBwcnVuZSB0aGUgdmFyaWFudHMuCgpgYGB7cn0KZXh0cmFjdEZVTkNPVEFUSU9OIDwtCiAgZnVuY3Rpb24odmNmLAogICAgICAgICAgIGZpZWxkcyA9IGMoCiAgICAgICAgICAgICAiR2VuY29kZV8zNF9odWdvU3ltYm9sIiwKICAgICAgICAgICAgICJHZW5jb2RlXzM0X3ZhcmlhbnRDbGFzc2lmaWNhdGlvbiIsCiAgICAgICAgICAgICAiR2VuY29kZV8zNF9zZWNvbmRhcnlWYXJpYW50Q2xhc3NpZmljYXRpb24iLAogICAgICAgICAgICAgIkdlbmNvZGVfMzRfdmFyaWFudFR5cGUiCiAgICAgICAgICAgKSkgewogICAgYW5ub3RhdGlvbl9jb2xuYW1lcyA8LQogICAgICBpbmZvKHZjZkBtZXRhZGF0YSRoZWFkZXIpWyJGVU5DT1RBVElPTiIsICJEZXNjcmlwdGlvbiJdICU+JQogICAgICBzdHJpbmdyOjpzdHJfcmVtb3ZlKCJGdW5jdGlvbmFsIGFubm90YXRpb24gZnJvbSB0aGUgRnVuY290YXRvciB0b29sLiAgRnVuY290YXRpb24gZmllbGRzIGFyZTogIikgJT4lCiAgICAgIHN0cmluZ3I6OnN0cl9zcGxpdChwYXR0ZXJuID0gIlxcfCIpICU+JQogICAgICB1bmxpc3QoKQogICAgCiAgICBhcy5kYXRhLmZyYW1lKGluZm8odmNmKVtbIkZVTkNPVEFUSU9OIl1dKSAlPiUKICAgICAgZHBseXI6OnNlbGVjdCgiRlVOQ09UQVRJT04iID0gdmFsdWUpICU+JQogICAgICBkcGx5cjo6bXV0YXRlKAogICAgICAgIEZVTkNPVEFUSU9OID0gd29yZChGVU5DT1RBVElPTiwgMSwgc2VwID0gIlxcXSIpLAogICAgICAgIEZVTkNPVEFUSU9OID0gc3RyX3JlbW92ZV9hbGwoRlVOQ09UQVRJT04sICJbXFxbXFxdXSIpCiAgICAgICkgJT4lCiAgICAgIHRpZHlyOjpzZXBhcmF0ZShGVU5DT1RBVElPTiwKICAgICAgICAgICAgICAgICAgICAgIGludG8gPSBhbm5vdGF0aW9uX2NvbG5hbWVzLAogICAgICAgICAgICAgICAgICAgICAgc2VwID0gIlxcfCIsCiAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gInJpZ2h0IikgJT4lCiAgICAgIGRwbHlyOjpzZWxlY3QoYWxsX29mKGZpZWxkcykpCiAgfQoKdmNmLmxpc3QuYW5ubyA8LSBsYXBwbHkodmNmLmxpc3QucWMsIGZ1bmN0aW9uKHgpIGV4dHJhY3RGVU5DT1RBVElPTih4KSkKCnNudi5saXN0IDwtIGxpc3QoKQpmb3Ioc2FtcGxlLm5hbWUgaW4gbmFtZXModmNmLmxpc3QucWMpKSB7CiAgdmNmIDwtIHZjZi5saXN0LnFjW1tzYW1wbGUubmFtZV1dCiAgZGYuYW5ubyA8LSB2Y2YubGlzdC5hbm5vW1tzYW1wbGUubmFtZV1dCiAgc252Lmxpc3RbW3NhbXBsZS5uYW1lXV0gPC0gYXMuZGF0YS5mcmFtZShnZW5vKHZjZilbWyJBRiJdXSkgJT4lCiAgICAKICAgICMgc2luY2UgdGhlIHNhbXBsZSBpcyB0aGUgc2FtZSBpbiBib3RoIFZDRiwgd2UgcmVuYW1lIHRoZSBBRiBjb2x1bW5zIGVxdWFsbHkKICAgIHNldE5hbWVzKC4sIGMoIkFGX0NUUkwiLCAiQUZfVFVNT1IiKSkgJT4lCiAgICAKICAgIG11dGF0ZV9hbGwoIH4gdW5saXN0KC4pKSAlPiUKICAgIGNiaW5kKC4sIGRmLmFubm8pICU+JQogICAgbXV0YXRlKAogICAgICBEUCA9IGluZm8odmNmKVtbIkRQIl1dLAogICAgICBUTE9EID0gaW5mbyh2Y2YpW1siVExPRCJdXSAlPiUgdW5saXN0KCkKICAgICkgJT4lCiAgICByb3duYW1lc190b19jb2x1bW4oIlZhcmlhbnQiKSAlPiUKICAgIG11dGF0ZShQb3NpdGlvbl9oZzM4ID0gd29yZChWYXJpYW50LCAxLCBzZXAgPSAiXyIpLAogICAgICAgICAgIC5iZWZvcmUgPSAiVmFyaWFudCIpICU+JQogICAgbXV0YXRlKFZhcmlhbnQgPSB3b3JkKFZhcmlhbnQsIDIsIHNlcCA9ICJfIikpCn0KCmxhcHBseShzbnYubGlzdCwgaGVhZCwgMTApICMgUHJpbnQgb25seSBmaXJzdCAxMCByb3dzCmBgYAoKKipWYXJpYW50IGZpbHRlcnMqKgoKTW9zdCBvZiB0aGVzZSBTTlZzIGFyZSBhcnRlZmFjdHMgdGhhdCB3ZSB3aWxsIG5lZWQgdG8gZmlsdGVyIG91dC4gCgpGaXJzdCwgd2UgY2FuIHJlbW92ZSBub24tY29kaW5nIHZhcmlhbnRzOiBpbnRyb25zLCB2YXJpYW50cyBmcm9tIGludGVyZ2VuaWMgcmVnaW9ucywgZXRjLiBOZXh0IGJsb2NrIG9mIGNvZGUgY3JlYXRlcyBhIGBzZWNvbmRfZmlsdGVyYCBjb2x1bW4gaW4gdGhlIGRhdGEgZnJhbWUgd2l0aCBTTlZzIHdoaWNoIHdpbGwgaGVscCB1cyBmaWx0ZXIgdGhlIG11dGF0aW9ucyB3aXRob3V0IGxvc2luZyBwb3NzaWJsZSBmYWxzZSBuZWdhdGl2ZXMuCgpgYGB7cn0Kbm9uY29kaW5nLnZhcnMgPC0gYygiSUdSIiwgIklOVFJPTiIsICJDT1VMRF9OT1RfREVURVJNSU5FIiwgIlJOQSIsICJTSUxFTlQiKQoKc252Lmxpc3QgPC0gc252Lmxpc3QgJT4lCiAgbGFwcGx5KAogIG11dGF0ZSwKICAgIHNlY29uZF9maWx0ZXIgPSBjYXNlX3doZW4oCiAgICAgIEdlbmNvZGVfMzRfdmFyaWFudENsYXNzaWZpY2F0aW9uICVpbiUgbm9uY29kaW5nLnZhcnMgfiAibm9uLWNvZGluZyIsCiAgICAgIEdlbmNvZGVfMzRfc2Vjb25kYXJ5VmFyaWFudENsYXNzaWZpY2F0aW9uICVpbiUgbm9uY29kaW5nLnZhcnMgfiAibm9uLWNvZGluZyIsCiAgICAgIFRSVUUgfiAiUEFTUyIKICAgICkKICApCgpsYXBwbHkoc252Lmxpc3QsIGZpbHRlciwgc2Vjb25kX2ZpbHRlciA9PSAiUEFTUyIpICU+JSAKICBsYXBwbHkoZGltKQpgYGAKCk5leHQsIHdlIGNhbiBmaWx0ZXIgdGhlIG11dGF0aW9ucyBieSB0aHJlc2hvbGRpbmcgdGhlbSBieSB0dW1vciBhbGxlbGUgZnJlcXVlbmN5ID4gMTAlIGFuZCBgVExPRGAgPiAxNS4gYFRMT0RgIHN0YW5kcyBmb3IgdHVtb3IgbG9nIG9kZHMgYW5kIGlzIGEgb2YgcHJvYmFiaWxpdHkgdGhhdCB0aGUgdmFyaWFudCBpcyBwcmVzZW50IGluIHRoZSB0dW1vciBzYW1wbGUgcmVsYXRpdmUgdG8gdGhlIGV4cGVjdGVkIG5vaXNlLgoKYGBge3J9CkFGX1RVTU9SX3RocmVzaCA8LSAwLjEKVExPRF90aHJlc2ggPC0gMTUKCnNudi5saXN0IDwtIHNudi5saXN0ICU+JQogIGxhcHBseSgKICAgIG11dGF0ZSwKICAgIHNlY29uZF9maWx0ZXIgPSBjYXNlX3doZW4oCiAgICAgIHNlY29uZF9maWx0ZXIgPT0gIlBBU1MiICYgVExPRCA8IFRMT0RfdGhyZXNoIH4gImxvd19UTE9EIiwKICAgICAgc2Vjb25kX2ZpbHRlciA9PSAiUEFTUyIgJgogICAgICAgIEFGX1RVTU9SIDwgQUZfVFVNT1JfdGhyZXNoIH4gImxvd19UVU1PUl9BRiIsCiAgICAgIFRSVUUgfiBzZWNvbmRfZmlsdGVyCiAgICApCiAgKQoKbGFwcGx5KHNudi5saXN0LCBmaWx0ZXIsIHNlY29uZF9maWx0ZXIgPT0gIlBBU1MiKSAlPiUgCiAgbGFwcGx5KGRpbSkKYGBgCgpTY2F0dGVycGxvdCBvZiAKCmBgYHtyLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD01fQptYXguVExPRCA8LSBiaW5kX3Jvd3Moc252Lmxpc3QpICU+JQogIHNlbGVjdChUTE9EKSAlPiUgbWF4KCkKbWluLlRMT0QgPC0gYmluZF9yb3dzKHNudi5saXN0KSAlPiUKICBzZWxlY3QoVExPRCkgJT4lIG1pbigpCgpsYXBwbHkobmFtZXMoc252Lmxpc3QpLAogICAgICAgZnVuY3Rpb24obmFtZSkgewogICAgICAgICBzbnYubGlzdFtbbmFtZV1dICU+JQogICAgICAgICAgIGZpbHRlcihzZWNvbmRfZmlsdGVyICE9ICJub24tY29kaW5nIikgJT4lCiAgICAgICAgICAgZ2dwbG90KGFlcyh5ID0gQUZfVFVNT1IsIHggPSBUTE9EKSkgKwogICAgICAgICAgIHRoZW1lX2J3KCkgKwogICAgICAgICAgIGxhYnModGl0bGUgPSBuYW1lKSArCiAgICAgICAgICAgZ2VvbV9wb2ludCgKICAgICAgICAgICAgIGFlcyhjb2xvciA9IEdlbmNvZGVfMzRfdmFyaWFudENsYXNzaWZpY2F0aW9uKSwKICAgICAgICAgICAgIHNoYXBlID0gMSwKICAgICAgICAgICAgIHNpemUgPSAyLAogICAgICAgICAgICAgc3Ryb2tlID0gMQogICAgICAgICAgICkgKwogICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQsIGxpbWl0cyA9IGMoMCwgMSkpICsKICAgICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoCiAgICAgICAgICAgICB0cmFucyA9ICJsb2cxMCIsCiAgICAgICAgICAgICBsaW1pdHMgPSBjKG1pbi5UTE9ELCBtYXguVExPRCksCiAgICAgICAgICAgICBicmVha3MgPSBjKDEsIDEwLCAyMCwgNTAsIDEwMCwgMjAwKQogICAgICAgICAgICkgKwogICAgICAgICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IFRMT0RfdGhyZXNoLCBsaW5ldHlwZSA9IDIpICsKICAgICAgICAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBBRl9UVU1PUl90aHJlc2gsIGxpbmV0eXBlID0gMikKICAgICAgIH0pCmBgYAoKKipDb21wYXJlIHRoZSBhbGxlbGUgZnJlcXVlbmNpZXMgZm9yIFNOViBkZXRlY3RlZCBpbiBib3RoIHNhbXBsZXMqKgoKTGV0J3MgY29tYmluZSB0aGUgYHNudi5saXN0YCBpbnRvIG9uZSBkYXRhIGZyYW1lIGFuZCBpbnNwZWN0IHRoZSBvdmVybGFwIG9mIGZpbHRlcmVkIG11dGF0aW9uczoKCmBgYHtyfQpjb21iaW5lZC5zbnYuZGYgPC0gc252Lmxpc3QgJT4lIAogIGJpbmRfcm93cyguaWQgPSAicGlwZWxpbmUiKSAlPiUgCiAgZmlsdGVyKHNlY29uZF9maWx0ZXIgPT0gIlBBU1MiKSAlPiUgCiAgZHBseXI6OnNlbGVjdChwaXBlbGluZSwgUG9zaXRpb25faGczOCwgVmFyaWFudCwgQUZfVFVNT1IsCiAgICAgICAgIEdlbmNvZGVfMzRfaHVnb1N5bWJvbCwgR2VuY29kZV8zNF92YXJpYW50Q2xhc3NpZmljYXRpb24pICU+JSAKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gInBpcGVsaW5lIiwgdmFsdWVzX2Zyb20gPSAiQUZfVFVNT1IiKQoKY29tYmluZWQuc252LmRmICU+JSAKICBtdXRhdGUoZGV0ZWN0aW9uID0gY2FzZV93aGVuKAogICAgIWlzLm5hKGBjbGFzc2ljIEdBVEtgKSAmICFpcy5uYShgUGFyYWJyaWNrcyBHQVRLYCkgfiAiY2xhc3NpYyBhbmQgUGFyYWJyaWNrcyIsCiAgICAhaXMubmEoYGNsYXNzaWMgR0FUS2ApICYgaXMubmEoYFBhcmFicmlja3MgR0FUS2ApIH4gImNsYXNzaWMiLAogICAgaXMubmEoYGNsYXNzaWMgR0FUS2ApICYgIWlzLm5hKGBQYXJhYnJpY2tzIEdBVEtgKSB+ICJQYXJhYnJpY2tzIgogICkpICU+JSAKICBncm91cF9ieShkZXRlY3Rpb24sIEdlbmNvZGVfMzRfdmFyaWFudENsYXNzaWZpY2F0aW9uKSAlPiUgCiAgc3VtbWFyaXNlKE4gPSBuKCkpICU+JSAKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gZGV0ZWN0aW9uLCB2YWx1ZXNfZnJvbSA9IE4pCmBgYAoKT25lIG9mIHRoZSBtb3N0IGNvbW1vbmx5IG11dGF0ZWQgZ2VuZXMgaW4gdHVtb3JzIGlzICpUUDUzKi4gVXNpbmcgZmlsdGVyIGJ5IGdlbmUgbmFtZSB3ZSBjYW4gY2hlY2sgdGhlIHByZXNlbmNlIG9mICpUUDUzKiBtdXRhdGlvbnMuCgpgYGB7cn0KZmlsdGVyKGNvbWJpbmVkLnNudi5kZiwgR2VuY29kZV8zNF9odWdvU3ltYm9sID09ICJUUDUzIikKYGBgCgpGaW5hbGx5LCBsZXRzIGNvbXBhcmUgdGhlIGFsbGVsZSBmcmVxdWVuY2llcyBmb3IgdGhlIHZhcmlhbnRzIGRldGVjdGVkIGJ5IGJvdGggcGlwZWxpbmVzOgoKYGBge3J9CmNvbWJpbmVkLnNudi5kZiAlPiUKICBnZ3Bsb3QoYWVzKHggPSBgY2xhc3NpYyBHQVRLYCwgeSA9IGBQYXJhYnJpY2tzIEdBVEtgKSkgKwogIGdlb21fcG9pbnQoYWVzKGZpbGwgPSBHZW5jb2RlXzM0X3ZhcmlhbnRDbGFzc2lmaWNhdGlvbiksCiAgICAgICAgICAgICBzaGFwZSA9IDIxLAogICAgICAgICAgICAgc2l6ZSA9IDIpICsKICB0aGVtZV9idygpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50LCBsaW1pdHMgPSBjKDAsIDEpKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCwgbGltaXRzID0gYygwLCAxKSkKYGBgCgoqKlNlc3Npb24gaW5mbyoqCgpgYGB7cn0Kc2Vzc2lvbkluZm8oKQpgYGAKCg==