Additional visualizations for gene/sample level QC assessment

Count boxplots

To evaluate the difference between the count distributions before and after normalization, let’s extract the raw counts and the rlog normalized counts, and coerce them to tibbles.

raw_counts = as_tibble(counts(dds), rownames = 'id')
norm_counts = as_tibble(assay(rld), rownames = 'id')

raw_counts
# A tibble: 55,492 × 7
   id                 sample_A sample_B sample_C sample_D sample_E sample_F
   <chr>                 <int>    <int>    <int>    <int>    <int>    <int>
 1 ENSMUSG00000000001     1041      905     1296     3481     1283     1921
 2 ENSMUSG00000000003        0        0        0        0        0        0
 3 ENSMUSG00000000028     1043     1232     1664     2690     1825     2720
 4 ENSMUSG00000000031     1819      914     1618     8618     1350     1222
 5 ENSMUSG00000000037       19       11       18       48       37       29
 6 ENSMUSG00000000049       18        1        4       24        1        1
 7 ENSMUSG00000000056    14972    14768    21026    22962    22263    23622
 8 ENSMUSG00000000058        1        0        0        0        0        0
 9 ENSMUSG00000000078      888      607      911     1689      738     1180
10 ENSMUSG00000000085      402      483      744      898      811     1261
# ℹ 55,482 more rows
norm_counts
# A tibble: 16,249 × 7
   id                 sample_A sample_B sample_C sample_D sample_E sample_F
   <chr>                 <dbl>    <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
 1 ENSMUSG00000000001    10.5     10.4     10.4     10.8     10.4     10.6 
 2 ENSMUSG00000000028    10.6     10.7     10.7     10.7     10.8     11.0 
 3 ENSMUSG00000000031    11.2     10.5     10.7     11.9     10.6     10.3 
 4 ENSMUSG00000000037     4.64     4.56     4.58     4.66     4.72     4.62
 5 ENSMUSG00000000049     3.02     2.82     2.84     2.94     2.81     2.81
 6 ENSMUSG00000000056    14.3     14.3     14.3     13.9     14.4     14.2 
 7 ENSMUSG00000000078    10.1      9.77     9.86     9.93     9.68     9.90
 8 ENSMUSG00000000085     9.28     9.41     9.53     9.23     9.60     9.80
 9 ENSMUSG00000000088    10.4     10.6     10.7     10.5     10.5     10.2 
10 ENSMUSG00000000093     3.88     3.89     3.93     3.91     3.94     3.99
# ℹ 16,239 more rows

Next, this is the perfect opportunity to use tidyr::pivot_longer() because we want to use ggplot() for the bar plots, but the data is currently in the wide form, not the tidy form!

tidy_raw = tidyr::pivot_longer(raw_counts, -id, names_to = 'sample', values_to = 'counts')
tidy_norm = tidyr::pivot_longer(norm_counts, -id, names_to = 'sample', values_to = 'counts')

We should also join in the sample metadata so that we can color on the sample groups.

samplesheet_tbl = as_tibble(colData(dds), rownames = 'sample')

tidy_raw = tidy_raw %>% left_join(samplesheet_tbl, by = 'sample')
tidy_norm = tidy_norm %>% left_join(samplesheet_tbl, by = 'sample')
raw_boxplot = ggplot(tidy_raw, aes(x = sample, y = log2(counts), fill = condition)) +
    geom_boxplot(notch = TRUE) +
    labs(
        title = 'Raw Counts',
        x = 'Sample',
        y = 'log2 Counts') +
    theme_bw() + theme(axis.text.x = element_text(angle = 90))
raw_boxplot
Warning: Removed 226538 rows containing non-finite outside the scale range
(`stat_boxplot()`).

Question

Why did we get that warning about non-finite values?

norm_boxplot = ggplot(tidy_norm, aes(x = sample, y = counts, fill = condition)) +
    geom_boxplot(notch = TRUE) +
    labs(
        title = 'rlog Normalized Counts',
        x = 'Sample',
        y = 'rlog Counts') +
    theme_bw() + theme(axis.text.x = element_text(angle = 90))
norm_boxplot

Observe that the normalized plots truly do appear normalized; their means are more uniform. Let’s go ahead and save these plots in our outputs/figures folder.

ggsave(filename = 'outputs/figures/Boxplot_raw_condition.pdf', plot = raw_boxplot, height = 6, width = 6)
ggsave(filename = 'outputs/figures/Boxplot_rlog_condition.pdf', plot = norm_boxplot, height = 6, width = 6)

Heatmaps

Let’s create a heatmap based on the distance between pair-wise sample expression profiles. This is another vantage point of how similar and dissimilar the samples are from one another. To get started, we actually want a plain matrix, with out a column for the gene IDs, as we did for the boxplot.

norm_mat = assay(rld)
head(norm_mat)
                    sample_A  sample_B  sample_C  sample_D  sample_E  sample_F
ENSMUSG00000000001 10.514813 10.366709 10.419463 10.840373 10.410449 10.578771
ENSMUSG00000000028 10.604461 10.734506 10.735026 10.682714 10.820938 10.990999
ENSMUSG00000000031 11.160276 10.498747 10.742763 11.861617 10.578156 10.298022
ENSMUSG00000000037  4.642433  4.555500  4.578934  4.656950  4.719012  4.620204
ENSMUSG00000000049  3.017478  2.820198  2.843894  2.936455  2.814833  2.811619
ENSMUSG00000000056 14.321672 14.284652 14.337197 13.904238 14.393912 14.235971

Next, we’ll use the dist() function on the transpose of norm_mat to compute the distance. We will use the default Euclidean distance.

dist_mat = dist(t(norm_mat), upper = TRUE)

Next, we’ll plot a very simple pheatmap() using this matrix. But let’s check the class of dist_mat first, because the input to pheatmap() needs to be a matrix.

class(dist_mat)
[1] "dist"
# Have to coerce it
dist_mat = as.matrix(dist_mat)

dist_heatmap = pheatmap(
    mat = dist_mat,
    cluster_rows = TRUE,
    cluster_cols = TRUE,
    show_rownames = TRUE,
    show_colnames = TRUE
)
dist_heatmap

This is nice, but there are a few tweaks we might consider:

  1. The color scale used by default is divergent, but our distances are values in [0, Inf), so a linear color scale would be more appropriate here.
  2. We might like for the sample metadata to be included so that we can easily tell if the samples cluster by their condition.

To accomplish the first change, we’ll use the colorRampPalette() function from the RColorBrewer package. This is the package to use for creating color scales of all varieties. We highly recommend exploring the package website and documentation.

colors = colorRampPalette(brewer.pal(9, 'Blues'))(50)

Now let’s use that in the pheatmap() call using the color parameter:

dist_heatmap = pheatmap(
    mat = dist_mat,
    color = colors,
    cluster_rows = TRUE,
    cluster_cols = TRUE,
    show_rownames = TRUE,
    show_colnames = TRUE
)
dist_heatmap

This is a lot more reasonable. More distant samples are a deeper blue, while more similar samples are closer to white. Next, let’s add some annotation data.

annotation_tbl = samplesheet_tbl %>%
    dplyr::select(sample, condition) %>%
    tibble::column_to_rownames(var = 'sample')

dist_heatmap = pheatmap(
    mat = dist_mat,
    color = colors,
    cluster_rows = TRUE,
    cluster_cols = TRUE,
    show_rownames = TRUE,
    show_colnames = TRUE,
    annotation_col = annotation_tbl
)
dist_heatmap

And now we have our conditions as a colored annotation bar along the columns.

Session Info

sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: x86_64-apple-darwin20
Running under: macOS Sonoma 14.4.1

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-x86_64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/Detroit
tzcode source: internal

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

other attached packages:
 [1] biomaRt_2.60.1              data.table_1.15.4          
 [3] RColorBrewer_1.1-3          pheatmap_1.0.12            
 [5] ggrepel_0.9.5               lubridate_1.9.3            
 [7] forcats_1.0.0               stringr_1.5.1              
 [9] dplyr_1.1.4                 purrr_1.0.2                
[11] readr_2.1.5                 tidyr_1.3.1                
[13] tibble_3.2.1                ggplot2_3.5.1              
[15] tidyverse_2.0.0             DESeq2_1.44.0              
[17] SummarizedExperiment_1.34.0 Biobase_2.64.0             
[19] MatrixGenerics_1.16.0       matrixStats_1.3.0          
[21] GenomicRanges_1.56.1        GenomeInfoDb_1.40.1        
[23] IRanges_2.38.1              S4Vectors_0.42.1           
[25] BiocGenerics_0.50.0         knitr_1.47                 
[27] rmarkdown_2.27             

loaded via a namespace (and not attached):
 [1] DBI_1.2.3               httr2_1.0.2             rlang_1.1.4            
 [4] magrittr_2.0.3          compiler_4.4.0          RSQLite_2.3.7          
 [7] png_0.1-8               vctrs_0.6.5             pkgconfig_2.0.3        
[10] crayon_1.5.3            fastmap_1.2.0           dbplyr_2.5.0           
[13] XVector_0.44.0          labeling_0.4.3          utf8_1.2.4             
[16] tzdb_0.4.0              UCSC.utils_1.0.0        bit_4.0.5              
[19] xfun_0.44               zlibbioc_1.50.0         cachem_1.1.0           
[22] jsonlite_1.8.8          progress_1.2.3          blob_1.2.4             
[25] highr_0.11              DelayedArray_0.30.1     BiocParallel_1.38.0    
[28] parallel_4.4.0          prettyunits_1.2.0       R6_2.5.1               
[31] bslib_0.7.0             stringi_1.8.4           jquerylib_0.1.4        
[34] Rcpp_1.0.13             Matrix_1.7-0            timechange_0.3.0       
[37] tidyselect_1.2.1        rstudioapi_0.16.0       abind_1.4-5            
[40] yaml_2.3.8              codetools_0.2-20        curl_5.2.1             
[43] lattice_0.22-6          withr_3.0.1             KEGGREST_1.44.1        
[46] evaluate_0.23           BiocFileCache_2.12.0    xml2_1.3.6             
[49] Biostrings_2.72.1       filelock_1.0.3          pillar_1.9.0           
[52] BiocManager_1.30.23     generics_0.1.3          hms_1.1.3              
[55] munsell_0.5.1           scales_1.3.0            glue_1.7.0             
[58] tools_4.4.0             locfit_1.5-9.10         grid_4.4.0             
[61] AnnotationDbi_1.66.0    colorspace_2.1-1        GenomeInfoDbData_1.2.12
[64] cli_3.6.2               rappdirs_0.3.3          fansi_1.0.6            
[67] S4Arrays_1.4.1          gtable_0.3.5            sass_0.4.9             
[70] digest_0.6.35           SparseArray_1.4.8       farver_2.1.2           
[73] memoise_2.0.1           htmltools_0.5.8.1       lifecycle_1.0.4        
[76] httr_1.4.7              bit64_4.0.5            

These materials have been adapted and extended from materials listed above. These are open access materials distributed under the terms of the Creative Commons Attribution license (CC BY 4.0), which permits unrestricted use, distribution, and reproduction in any medium, provided the original author and source are credited.




Previous lesson Top of this lesson To wrap-up
LS0tCnRpdGxlOiAiUiAtIEJvbnVzIENvbnRlbnQiCmF1dGhvcjogIlVNIEJpb2luZm9ybWF0aWNzIENvcmUiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogICAgICAgIGh0bWxfZG9jdW1lbnQ6CiAgICAgICAgICAgIGluY2x1ZGVzOgogICAgICAgICAgICAgICAgaW5faGVhZGVyOiBoZWFkZXIuaHRtbAogICAgICAgICAgICB0aGVtZTogcGFwZXIKICAgICAgICAgICAgdG9jOiB0cnVlCiAgICAgICAgICAgIHRvY19kZXB0aDogNAogICAgICAgICAgICB0b2NfZmxvYXQ6IHRydWUKICAgICAgICAgICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgICAgICAgICBmaWdfY2FwdGlvbjogdHJ1ZQogICAgICAgICAgICBtYXJrZG93bjogR0ZNCiAgICAgICAgICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgpib2R5LCB0ZCB7CiAgIGZvbnQtc2l6ZTogMThweDsKfQpjb2RlLnJ7CiAgZm9udC1zaXplOiAxMnB4Owp9CnByZSB7CiAgZm9udC1zaXplOiAxMnB4Cn0KPC9zdHlsZT4KCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CnNvdXJjZSgiLi4vYmluL2NodW5rLW9wdGlvbnMuUiIpCmtuaXRyX2ZpZ19wYXRoKCJSX2JvbnVzX2NvbnRlbnQiKQpgYGAKCmBgYHtyIE1vZHVsZXMsIGV2YWw9VFJVRSwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShERVNlcTIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdncmVwZWwpCmxpYnJhcnkocGhlYXRtYXApCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpgYGAKCkFkZGl0aW9uYWwgdmlzdWFsaXphdGlvbnMgZm9yIGdlbmUvc2FtcGxlIGxldmVsIFFDIGFzc2Vzc21lbnQKCjwhLS0tIEltcHJvdmVtZW50IC0gYWRkIGdlbmUgbGV2ZWwgaGVhdG1hcCB2aXN1YWxpemF0aW9ucyAtLT4KCiMgQ291bnQgYm94cGxvdHMKClRvIGV2YWx1YXRlIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGNvdW50IGRpc3RyaWJ1dGlvbnMgYmVmb3JlIGFuZCBhZnRlciBub3JtYWxpemF0aW9uLCBsZXQncyBleHRyYWN0IHRoZSByYXcgY291bnRzIGFuZCB0aGUgcmxvZyBub3JtYWxpemVkIGNvdW50cywgYW5kIGNvZXJjZSB0aGVtIHRvIGB0aWJibGVgcy4KCmBgYHtyIFJhd0NvdW50c1NldHVwfQpyYXdfY291bnRzID0gYXNfdGliYmxlKGNvdW50cyhkZHMpLCByb3duYW1lcyA9ICdpZCcpCm5vcm1fY291bnRzID0gYXNfdGliYmxlKGFzc2F5KHJsZCksIHJvd25hbWVzID0gJ2lkJykKCnJhd19jb3VudHMKbm9ybV9jb3VudHMKYGBgCgpOZXh0LCB0aGlzIGlzIHRoZSAqKnBlcmZlY3QqKiBvcHBvcnR1bml0eSB0byB1c2UgYHRpZHlyOjpwaXZvdF9sb25nZXIoKWAgYmVjYXVzZSB3ZSB3YW50IHRvIHVzZSBgZ2dwbG90KClgIGZvciB0aGUgYmFyIHBsb3RzLCBidXQgdGhlIGRhdGEgaXMgY3VycmVudGx5IGluIHRoZSB3aWRlIGZvcm0sIG5vdCB0aGUgdGlkeSBmb3JtIQoKYGBge3IgUGl2b3RMb25nZXJ9CnRpZHlfcmF3ID0gdGlkeXI6OnBpdm90X2xvbmdlcihyYXdfY291bnRzLCAtaWQsIG5hbWVzX3RvID0gJ3NhbXBsZScsIHZhbHVlc190byA9ICdjb3VudHMnKQp0aWR5X25vcm0gPSB0aWR5cjo6cGl2b3RfbG9uZ2VyKG5vcm1fY291bnRzLCAtaWQsIG5hbWVzX3RvID0gJ3NhbXBsZScsIHZhbHVlc190byA9ICdjb3VudHMnKQpgYGAKCldlIHNob3VsZCBhbHNvIGpvaW4gaW4gdGhlIHNhbXBsZSBtZXRhZGF0YSBzbyB0aGF0IHdlIGNhbiBjb2xvciBvbiB0aGUgc2FtcGxlIGdyb3Vwcy4KCmBgYHtyIEpvaW5NZXRhZGF0YX0Kc2FtcGxlc2hlZXRfdGJsID0gYXNfdGliYmxlKGNvbERhdGEoZGRzKSwgcm93bmFtZXMgPSAnc2FtcGxlJykKCnRpZHlfcmF3ID0gdGlkeV9yYXcgJT4lIGxlZnRfam9pbihzYW1wbGVzaGVldF90YmwsIGJ5ID0gJ3NhbXBsZScpCnRpZHlfbm9ybSA9IHRpZHlfbm9ybSAlPiUgbGVmdF9qb2luKHNhbXBsZXNoZWV0X3RibCwgYnkgPSAnc2FtcGxlJykKYGBgCgpgYGB7ciBSYXdCb3hwbG90fQpyYXdfYm94cGxvdCA9IGdncGxvdCh0aWR5X3JhdywgYWVzKHggPSBzYW1wbGUsIHkgPSBsb2cyKGNvdW50cyksIGZpbGwgPSBjb25kaXRpb24pKSArCiAgICBnZW9tX2JveHBsb3Qobm90Y2ggPSBUUlVFKSArCiAgICBsYWJzKAogICAgICAgIHRpdGxlID0gJ1JhdyBDb3VudHMnLAogICAgICAgIHggPSAnU2FtcGxlJywKICAgICAgICB5ID0gJ2xvZzIgQ291bnRzJykgKwogICAgdGhlbWVfYncoKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQpyYXdfYm94cGxvdApgYGAKCj4gIyBRdWVzdGlvbiB7LnVubGlzdGVkIC51bm51bWJlcmVkfQo+IFdoeSBkaWQgd2UgZ2V0IHRoYXQgd2FybmluZyBhYm91dCBub24tZmluaXRlIHZhbHVlcz8KCmBgYHtyIE5vcm1Cb3hwbG90fQpub3JtX2JveHBsb3QgPSBnZ3Bsb3QodGlkeV9ub3JtLCBhZXMoeCA9IHNhbXBsZSwgeSA9IGNvdW50cywgZmlsbCA9IGNvbmRpdGlvbikpICsKICAgIGdlb21fYm94cGxvdChub3RjaCA9IFRSVUUpICsKICAgIGxhYnMoCiAgICAgICAgdGl0bGUgPSAncmxvZyBOb3JtYWxpemVkIENvdW50cycsCiAgICAgICAgeCA9ICdTYW1wbGUnLAogICAgICAgIHkgPSAncmxvZyBDb3VudHMnKSArCiAgICB0aGVtZV9idygpICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpCm5vcm1fYm94cGxvdApgYGAKCk9ic2VydmUgdGhhdCB0aGUgbm9ybWFsaXplZCBwbG90cyB0cnVseSBkbyBhcHBlYXIgbm9ybWFsaXplZDsgdGhlaXIgbWVhbnMgYXJlIG1vcmUgdW5pZm9ybS4gTGV0J3MgZ28gYWhlYWQgYW5kIHNhdmUgdGhlc2UgcGxvdHMgaW4gb3VyIGBvdXRwdXRzL2ZpZ3VyZXNgIGZvbGRlci4KCmBgYHtyIEJveHBsb3RzUmF3U2F2ZSwgZXZhbCA9IEZBTFNFfQpnZ3NhdmUoZmlsZW5hbWUgPSAnb3V0cHV0cy9maWd1cmVzL0JveHBsb3RfcmF3X2NvbmRpdGlvbi5wZGYnLCBwbG90ID0gcmF3X2JveHBsb3QsIGhlaWdodCA9IDYsIHdpZHRoID0gNikKZ2dzYXZlKGZpbGVuYW1lID0gJ291dHB1dHMvZmlndXJlcy9Cb3hwbG90X3Jsb2dfY29uZGl0aW9uLnBkZicsIHBsb3QgPSBub3JtX2JveHBsb3QsIGhlaWdodCA9IDYsIHdpZHRoID0gNikKYGBgCgojIEhlYXRtYXBzCgpMZXQncyBjcmVhdGUgYSBoZWF0bWFwIGJhc2VkIG9uIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHBhaXItd2lzZSBzYW1wbGUgZXhwcmVzc2lvbiBwcm9maWxlcy4gVGhpcyBpcyBhbm90aGVyIHZhbnRhZ2UgcG9pbnQgb2YgaG93IHNpbWlsYXIgYW5kIGRpc3NpbWlsYXIgdGhlIHNhbXBsZXMgYXJlIGZyb20gb25lIGFub3RoZXIuIFRvIGdldCBzdGFydGVkLCB3ZSBhY3R1YWxseSB3YW50IGEgcGxhaW4gbWF0cml4LCB3aXRoIG91dCBhIGNvbHVtbiBmb3IgdGhlIGdlbmUgSURzLCBhcyB3ZSBkaWQgZm9yIHRoZSBib3hwbG90LgoKYGBge3IgTm9ybUNvdW50c30Kbm9ybV9tYXQgPSBhc3NheShybGQpCmhlYWQobm9ybV9tYXQpCmBgYAoKTmV4dCwgd2UnbGwgdXNlIHRoZSBgZGlzdCgpYCBmdW5jdGlvbiBvbiB0aGUgdHJhbnNwb3NlIG9mIGBub3JtX21hdGAgdG8gY29tcHV0ZSB0aGUgZGlzdGFuY2UuIFdlIHdpbGwgdXNlIHRoZSBkZWZhdWx0IEV1Y2xpZGVhbiBkaXN0YW5jZS4KCmBgYHtyIERpc3RhbmNlTWF0fQpkaXN0X21hdCA9IGRpc3QodChub3JtX21hdCksIHVwcGVyID0gVFJVRSkKYGBgCgpOZXh0LCB3ZSdsbCBwbG90IGEgdmVyeSBzaW1wbGUgYHBoZWF0bWFwKClgIHVzaW5nIHRoaXMgbWF0cml4LiBCdXQgbGV0J3MgY2hlY2sgdGhlIGNsYXNzIG9mIGBkaXN0X21hdGAgZmlyc3QsIGJlY2F1c2UgdGhlIGlucHV0IHRvIGBwaGVhdG1hcCgpYCBuZWVkcyB0byBiZSBhIGBtYXRyaXhgLgoKYGBge3IgRGlzdGFuY2VIZWF0bWFwfQpjbGFzcyhkaXN0X21hdCkKCiMgSGF2ZSB0byBjb2VyY2UgaXQKZGlzdF9tYXQgPSBhcy5tYXRyaXgoZGlzdF9tYXQpCgpkaXN0X2hlYXRtYXAgPSBwaGVhdG1hcCgKICAgIG1hdCA9IGRpc3RfbWF0LAogICAgY2x1c3Rlcl9yb3dzID0gVFJVRSwKICAgIGNsdXN0ZXJfY29scyA9IFRSVUUsCiAgICBzaG93X3Jvd25hbWVzID0gVFJVRSwKICAgIHNob3dfY29sbmFtZXMgPSBUUlVFCikKZGlzdF9oZWF0bWFwCmBgYAoKVGhpcyBpcyBuaWNlLCBidXQgdGhlcmUgYXJlIGEgZmV3IHR3ZWFrcyB3ZSBtaWdodCBjb25zaWRlcjoKCjEuIFRoZSBjb2xvciBzY2FsZSB1c2VkIGJ5IGRlZmF1bHQgaXMgZGl2ZXJnZW50LCBidXQgb3VyIGRpc3RhbmNlcyBhcmUgdmFsdWVzIGluIFswLCBJbmYpLCBzbyBhIGxpbmVhciBjb2xvciBzY2FsZSB3b3VsZCBiZSBtb3JlIGFwcHJvcHJpYXRlIGhlcmUuCjIuIFdlIG1pZ2h0IGxpa2UgZm9yIHRoZSBzYW1wbGUgbWV0YWRhdGEgdG8gYmUgaW5jbHVkZWQgc28gdGhhdCB3ZSBjYW4gZWFzaWx5IHRlbGwgaWYgdGhlIHNhbXBsZXMgY2x1c3RlciBieSB0aGVpciBgY29uZGl0aW9uYC4KClRvIGFjY29tcGxpc2ggdGhlIGZpcnN0IGNoYW5nZSwgd2UnbGwgdXNlIHRoZSBgY29sb3JSYW1wUGFsZXR0ZSgpYCBmdW5jdGlvbiBmcm9tIHRoZSBgUkNvbG9yQnJld2VyYCBwYWNrYWdlLiBUaGlzIGlzICoqdGhlKiogcGFja2FnZSB0byB1c2UgZm9yIGNyZWF0aW5nIGNvbG9yIHNjYWxlcyBvZiBhbGwgdmFyaWV0aWVzLiBXZSBoaWdobHkgcmVjb21tZW5kIGV4cGxvcmluZyB0aGUgcGFja2FnZSB3ZWJzaXRlIGFuZCBkb2N1bWVudGF0aW9uLgoKYGBge3IgQ2hvb3NlQ29sb3JzfQpjb2xvcnMgPSBjb2xvclJhbXBQYWxldHRlKGJyZXdlci5wYWwoOSwgJ0JsdWVzJykpKDUwKQpgYGAKCk5vdyBsZXQncyB1c2UgdGhhdCBpbiB0aGUgYHBoZWF0bWFwKClgIGNhbGwgdXNpbmcgdGhlIGBjb2xvcmAgcGFyYW1ldGVyOgoKYGBge3IgRGlzdGFuY2VIZWF0bWFDb2xvcnN9CmRpc3RfaGVhdG1hcCA9IHBoZWF0bWFwKAogICAgbWF0ID0gZGlzdF9tYXQsCiAgICBjb2xvciA9IGNvbG9ycywKICAgIGNsdXN0ZXJfcm93cyA9IFRSVUUsCiAgICBjbHVzdGVyX2NvbHMgPSBUUlVFLAogICAgc2hvd19yb3duYW1lcyA9IFRSVUUsCiAgICBzaG93X2NvbG5hbWVzID0gVFJVRQopCmRpc3RfaGVhdG1hcApgYGAKClRoaXMgaXMgYSBsb3QgbW9yZSByZWFzb25hYmxlLiBNb3JlIGRpc3RhbnQgc2FtcGxlcyBhcmUgYSBkZWVwZXIgYmx1ZSwgd2hpbGUgbW9yZSBzaW1pbGFyIHNhbXBsZXMgYXJlIGNsb3NlciB0byB3aGl0ZS4gTmV4dCwgbGV0J3MgYWRkIHNvbWUgYW5ub3RhdGlvbiBkYXRhLgoKYGBge3IgRGlzdGFuY2VIZWF0bWFwQW5ub3RhdGVkfQphbm5vdGF0aW9uX3RibCA9IHNhbXBsZXNoZWV0X3RibCAlPiUKICAgIGRwbHlyOjpzZWxlY3Qoc2FtcGxlLCBjb25kaXRpb24pICU+JQogICAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXModmFyID0gJ3NhbXBsZScpCgpkaXN0X2hlYXRtYXAgPSBwaGVhdG1hcCgKICAgIG1hdCA9IGRpc3RfbWF0LAogICAgY29sb3IgPSBjb2xvcnMsCiAgICBjbHVzdGVyX3Jvd3MgPSBUUlVFLAogICAgY2x1c3Rlcl9jb2xzID0gVFJVRSwKICAgIHNob3dfcm93bmFtZXMgPSBUUlVFLAogICAgc2hvd19jb2xuYW1lcyA9IFRSVUUsCiAgICBhbm5vdGF0aW9uX2NvbCA9IGFubm90YXRpb25fdGJsCikKZGlzdF9oZWF0bWFwCmBgYAoKQW5kIG5vdyB3ZSBoYXZlIG91ciBjb25kaXRpb25zIGFzIGEgY29sb3JlZCBhbm5vdGF0aW9uIGJhciBhbG9uZyB0aGUgY29sdW1ucy4KCiMgU291cmNlcwoKKiBIQkMgUUMgdHV0b3JpYWw6IGh0dHBzOi8vaGJjdHJhaW5pbmcuZ2l0aHViLmlvL0RHRV93b3Jrc2hvcC9sZXNzb25zLzAzX0RHRV9RQ19hbmFseXNpcy5odG1sCiogRGV0YWlsZWQgSGVhdG1hcCB0dXRvcmlhbCBmcm9tIEdhbGF4eTogaHR0cHM6Ly90cmFpbmluZy5nYWxheHlwcm9qZWN0Lm9yZy90cmFpbmluZy1tYXRlcmlhbC90b3BpY3MvdHJhbnNjcmlwdG9taWNzL3R1dG9yaWFscy9ybmEtc2VxLXZpei13aXRoLWhlYXRtYXAyL3R1dG9yaWFsLmh0bWwKCi0tLQoKIyBTZXNzaW9uIEluZm8KYGBge3IgU2Vzc2lvbkluZm99CnNlc3Npb25JbmZvKCkKYGBgCgotLS0KClRoZXNlIG1hdGVyaWFscyBoYXZlIGJlZW4gYWRhcHRlZCBhbmQgZXh0ZW5kZWQgZnJvbSBtYXRlcmlhbHMgbGlzdGVkIGFib3ZlLiBUaGVzZSBhcmUgb3BlbiBhY2Nlc3MgbWF0ZXJpYWxzIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSB0ZXJtcyBvZiB0aGUgW0NyZWF0aXZlIENvbW1vbnMgQXR0cmlidXRpb24gbGljZW5zZSAoQ0MgQlkgNC4wKV0oaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnkvNC4wLyksIHdoaWNoIHBlcm1pdHMgdW5yZXN0cmljdGVkIHVzZSwgZGlzdHJpYnV0aW9uLCBhbmQgcmVwcm9kdWN0aW9uIGluIGFueSBtZWRpdW0sIHByb3ZpZGVkIHRoZSBvcmlnaW5hbCBhdXRob3IgYW5kIHNvdXJjZSBhcmUgY3JlZGl0ZWQuCgo8YnIvPgo8YnIvPgo8aHIvPgp8IFtQcmV2aW91cyBsZXNzb25dKE1vZHVsZTEyX0RFQW5ub3RhdGlvbnMuUm1kLmh0bWwpIHwgW1RvcCBvZiB0aGlzIGxlc3Nvbl0oI3RvcCkgfCBbVG8gd3JhcC11cF0oTW9kdWxlOTlfV3JhcF91cC5odG1sKSB8CnwgOi0tLSB8IDotLS0tOiB8IC0tLTogfA==