logo

This workshop will guide you through the basics of ChIP-seq analysis with hands-on exercises. You will learn how to process ChIP-seq data: perform read alignment, peak calling, quality control, visualization through the genome browser, motif finding and gene set enrichment analysis.

In this tutorial, we are going to focus on performing read alignment.

The dataset that we will be using today is ChIP-seq for HIF1a and HIF2a. HIF1a and HIF2a are the two principal isoforms of the Hypoxia-inducible factor (HIF), which is the major transcriptional regulator of cellular responses to hypoxia. This dataset was generated by Smythies et al (2018), and you can find the paper here. Because the original data is very big, we are going to work with a subset of the original reads from the study (reads that map to a segment of the chromosome 6).

Note: All the code in this tutorial will be performed in the terminal.

Let’s move to the scripts directory (or stay here if already there):

cd chip_seq/scripts

01 - Mapping with bowtie2

You can check the help on how to use bowtie2 by typing:

bowtie2 --help 
Bowtie 2 version 2.3.5.1 by Ben Langmead (langmea@cs.jhu.edu, www.cs.jhu.edu/~langmea)
Usage: 
  bowtie2 [options]* -x <bt2-idx> {-1 <m1> -2 <m2> | -U <r> | --interleaved <i> | -b <bam>} [-S <sam>]

  <bt2-idx>  Index filename prefix (minus trailing .X.bt2).
             NOTE: Bowtie 1 and Bowtie 2 indexes are not compatible.
  <m1>       Files with #1 mates, paired with files in <m2>.
             Could be gzip'ed (extension: .gz) or bzip2'ed (extension: .bz2).
  <m2>       Files with #2 mates, paired with files in <m1>.
             Could be gzip'ed (extension: .gz) or bzip2'ed (extension: .bz2).
  <r>        Files with unpaired reads.
             Could be gzip'ed (extension: .gz) or bzip2'ed (extension: .bz2).
  <i>        Files with interleaved paired-end FASTQ/FASTA reads
             Could be gzip'ed (extension: .gz) or bzip2'ed (extension: .bz2).
  <bam>      Files are unaligned BAM sorted by read name.
  <sam>      File for SAM output (default: stdout)

  <m1>, <m2>, <r> can be comma-separated lists (no whitespace) and can be
  specified many times.  E.g. '-U file1.fq,file2.fq -U file3.fq'.

Options (defaults in parentheses):

 Input:
  -q                 query input files are FASTQ .fq/.fastq (default)
  --tab5             query input files are TAB5 .tab5
  --tab6             query input files are TAB6 .tab6
  --qseq             query input files are in Illumina's qseq format
  -f                 query input files are (multi-)FASTA .fa/.mfa
  -r                 query input files are raw one-sequence-per-line
  -F k:<int>,i:<int> query input files are continuous FASTA where reads
                     are substrings (k-mers) extracted from a FASTA file <s>
                     and aligned at offsets 1, 1+i, 1+2i ... end of reference
  -c                 <m1>, <m2>, <r> are sequences themselves, not files
  -s/--skip <int>    skip the first <int> reads/pairs in the input (none)
  -u/--upto <int>    stop after first <int> reads/pairs (no limit)
  -5/--trim5 <int>   trim <int> bases from 5'/left end of reads (0)
  -3/--trim3 <int>   trim <int> bases from 3'/right end of reads (0)
  --trim-to [3:|5:]<int> trim reads exceeding <int> bases from either 3' or 5' end
                     If the read end is not specified then it defaults to 3 (0)
  --phred33          qualities are Phred+33 (default)
  --phred64          qualities are Phred+64
  --int-quals        qualities encoded as space-delimited integers

 Presets:                 Same as:
  For --end-to-end:
   --very-fast            -D 5 -R 1 -N 0 -L 22 -i S,0,2.50
   --fast                 -D 10 -R 2 -N 0 -L 22 -i S,0,2.50
   --sensitive            -D 15 -R 2 -N 0 -L 22 -i S,1,1.15 (default)
   --very-sensitive       -D 20 -R 3 -N 0 -L 20 -i S,1,0.50

  For --local:
   --very-fast-local      -D 5 -R 1 -N 0 -L 25 -i S,1,2.00
   --fast-local           -D 10 -R 2 -N 0 -L 22 -i S,1,1.75
   --sensitive-local      -D 15 -R 2 -N 0 -L 20 -i S,1,0.75 (default)
   --very-sensitive-local -D 20 -R 3 -N 0 -L 20 -i S,1,0.50

 Alignment:
  -N <int>           max # mismatches in seed alignment; can be 0 or 1 (0)
  -L <int>           length of seed substrings; must be >3, <32 (22)
  -i <func>          interval between seed substrings w/r/t read len (S,1,1.15)
  --n-ceil <func>    func for max # non-A/C/G/Ts permitted in aln (L,0,0.15)
  --dpad <int>       include <int> extra ref chars on sides of DP table (15)
  --gbar <int>       disallow gaps within <int> nucs of read extremes (4)
  --ignore-quals     treat all quality values as 30 on Phred scale (off)
  --nofw             do not align forward (original) version of read (off)
  --norc             do not align reverse-complement version of read (off)
  --no-1mm-upfront   do not allow 1 mismatch alignments before attempting to
                     scan for the optimal seeded alignments
  --end-to-end       entire read must align; no clipping (on)
   OR
  --local            local alignment; ends might be soft clipped (off)

 Scoring:
  --ma <int>         match bonus (0 for --end-to-end, 2 for --local) 
  --mp <int>         max penalty for mismatch; lower qual = lower penalty (6)
  --np <int>         penalty for non-A/C/G/Ts in read/ref (1)
  --rdg <int>,<int>  read gap open, extend penalties (5,3)
  --rfg <int>,<int>  reference gap open, extend penalties (5,3)
  --score-min <func> min acceptable alignment score w/r/t read length
                     (G,20,8 for local, L,-0.6,-0.6 for end-to-end)

 Reporting:
  (default)          look for multiple alignments, report best, with MAPQ
   OR
  -k <int>           report up to <int> alns per read; MAPQ not meaningful
   OR
  -a/--all           report all alignments; very slow, MAPQ not meaningful

 Effort:
  -D <int>           give up extending after <int> failed extends in a row (15)
  -R <int>           for reads w/ repetitive seeds, try <int> sets of seeds (2)

 Paired-end:
  -I/--minins <int>  minimum fragment length (0)
  -X/--maxins <int>  maximum fragment length (500)
  --fr/--rf/--ff     -1, -2 mates align fw/rev, rev/fw, fw/fw (--fr)
  --no-mixed         suppress unpaired alignments for paired reads
  --no-discordant    suppress discordant alignments for paired reads
  --dovetail         concordant when mates extend past each other
  --no-contain       not concordant when one mate alignment contains other
  --no-overlap       not concordant when mates overlap at all

 BAM:
  --align-paired-reads Bowtie2 will, by default, attempt to align unpaired BAM reads.
                     Use this option to align paired-end reads instead.
  --preserve-tags    Preserve tags from the original BAM record by
                     appending them to the end of the corresponding SAM output.

 Output:
  -t/--time          print wall-clock time taken by search phases
  --un <path>        write unpaired reads that didn't align to <path>
  --al <path>        write unpaired reads that aligned at least once to <path>
  --un-conc <path>   write pairs that didn't align concordantly to <path>
  --al-conc <path>   write pairs that aligned concordantly at least once to <path>
    (Note: for --un, --al, --un-conc, or --al-conc, add '-gz' to the option name, e.g.
    --un-gz <path>, to gzip compress output, or add '-bz2' to bzip2 compress output.)
  --quiet            print nothing to stderr except serious errors
  --met-file <path>  send metrics to file at <path> (off)
  --met-stderr       send metrics to stderr (off)
  --met <int>        report internal counters & metrics every <int> secs (1)
  --no-unal          suppress SAM records for unaligned reads
  --no-head          suppress header lines, i.e. lines starting with @
  --no-sq            suppress @SQ header lines
  --rg-id <text>     set read group id, reflected in @RG line and RG:Z: opt field
  --rg <text>        add <text> ("lab:value") to @RG line of SAM header.
                     Note: @RG line only printed when --rg-id is set.
  --omit-sec-seq     put '*' in SEQ and QUAL fields for secondary alignments.
  --sam-no-qname-trunc Suppress standard behavior of truncating readname at first whitespace 
                      at the expense of generating non-standard SAM.
  --xeq              Use '='/'X', instead of 'M,' to specify matches/mismatches in SAM record.
  --soft-clipped-unmapped-tlen Exclude soft-clipped bases when reporting TLEN

 Performance:
  -p/--threads <int> number of alignment threads to launch (1)
  --reorder          force SAM output order to match order of input reads
  --mm               use memory-mapped I/O for index; many 'bowtie's can share

 Other:
  --qc-filter        filter out reads that are bad according to QSEQ filter
  --seed <int>       seed for random number generator (0)
  --non-deterministic seed rand. gen. arbitrarily instead of using read attributes
  --version          print version information and quit
  -h/--help          print this usage message

You can also access the bowtie2 manual in here.

The data that we have is paired end data, so we have 2 .fastq files for one sample. They look like this:

ls ../data/fastq
HIF1a_Rep1_R1.fastq.gz
HIF1a_Rep1_R2.fastq.gz
HIF2a_Rep1_R1.fastq.gz
HIF2a_Rep1_R2.fastq.gz

Bowtie2 requires a genome index (compressed files that store the reference sequence). For this time, we’ve already generated the index and can be found here:

ls ../data/reference/GRCh38_chr6
hg38_chr6.1.bt2
hg38_chr6.2.bt2
hg38_chr6.3.bt2
hg38_chr6.4.bt2
hg38_chr6.rev.1.bt2
hg38_chr6.rev.2.bt2

This index was created only using the human chromosome 6. You can create your own index or download pre-built ones from the bowtie2 page.

Let’s create a directory where to store our alignment

mkdir -p ../results

Now we are ready to run bowtie2!

bowtie2 --very-sensitive-local -x ../data/reference/GRCh38_chr6/hg38_chr6 -1 ../data/fastq/HIF1a_Rep1_R1.fastq.gz -2 ../data/fastq/HIF1a_Rep1_R2.fastq.gz -S ../results/HIF1a_Rep1.sam
121159 reads; of these:
  121159 (100.00%) were paired; of these:
    1526 (1.26%) aligned concordantly 0 times
    103901 (85.76%) aligned concordantly exactly 1 time
    15732 (12.98%) aligned concordantly >1 times
    ----
    1526 pairs aligned concordantly 0 times; of these:
      928 (60.81%) aligned discordantly 1 time
    ----
    598 pairs aligned 0 times concordantly or discordantly; of these:
      1196 mates make up the pairs; of these:
        306 (25.59%) aligned 0 times
        456 (38.13%) aligned exactly 1 time
        434 (36.29%) aligned >1 times
99.87% overall alignment rate

Let’s take a look at the parameters that we’ve used for running bowtie2:

  • –very-sensitive-local: Bowtie2 comes with some useful combinations of parameters packaged into shorter “preset” parameters. The preset options that come with Bowtie 2 are designed to cover a wide area of the speed/sensitivity/accuracy trade-off space. “Fast” presets are generally faster but less sensitive, while the “sensitive” presets are generally slower but more sensitive and accurate. The local part means that we are doing ‘local’ alignment (remember that the default in Bowtie2 is the end-to-end alignment).
  • –x: path to the index
  • -1: path to fastq1
  • -2: path to fastq2
  • -S: path to the output SAM file

02 - Filtering SAM/BAM

For the following steps, we will be using samtools. The manual can be found here. Samtools is a compendium of various tools:

samtools --help

Program: samtools (Tools for alignments in the SAM format)
Version: 1.10 (using htslib 1.10.2-3ubuntu0.1)

Usage:   samtools <command> [options]

Commands:
  -- Indexing
     dict           create a sequence dictionary file
     faidx          index/extract FASTA
     fqidx          index/extract FASTQ
     index          index alignment

  -- Editing
     calmd          recalculate MD/NM tags and '=' bases
     fixmate        fix mate information
     reheader       replace BAM header
     targetcut      cut fosmid regions (for fosmid pool only)
     addreplacerg   adds or replaces RG tags
     markdup        mark duplicates

  -- File operations
     collate        shuffle and group alignments by name
     cat            concatenate BAMs
     merge          merge sorted alignments
     mpileup        multi-way pileup
     sort           sort alignment file
     split          splits a file by read group
     quickcheck     quickly check if SAM/BAM/CRAM file appears intact
     fastq          converts a BAM to a FASTQ
     fasta          converts a BAM to a FASTA

  -- Statistics
     bedcov         read depth per BED region
     coverage       alignment depth and percent coverage
     depth          compute the depth
     flagstat       simple stats
     idxstats       BAM index stats
     phase          phase heterozygotes
     stats          generate stats (former bamcheck)

  -- Viewing
     flags          explain BAM flags
     tview          text alignment viewer
     view           SAM<->BAM<->CRAM conversion
     depad          convert padded BAM to unpadded BAM

We can count the number of reads that are in our SAM

samtools view -c ../results/HIF1a_Rep1.sam
242318
  • -c: Instead of printing the alignments, only count them and print the total number

We need to convert the SAM file into a BAM file. In this same step, we will perform some filtering of the alignments:

samtools view -h -F 1804 -q 30 -b ../results/HIF1a_Rep1.sam -o ../results/HIF1a_Rep1.bam
  • -h: include header in the output
  • -F: Exclude alignments with a given FLAG field. The 1804 FLAG excludes the following reads: read unmapped, mate unmapped, not primary alignment, read quality low, PCR / optical duplicate. You can check the meaning of the SAM FLAGS in here. More information about samtools flags can be found here.
  • -q: Skip alignments with MAPQ smaller than the given value. The MAPQ is a non-negative integer Q = -10 log10 p, where p is an estimate of the probability that the alignment does not correspond to the read’s true point of origin. Usually, a good value is between 10-30.
  • -b: Output in the BAM format.
  • -o: Output to file

We can also remove reads that map blacklisted regions by ENCODE using bedtools.

bedtools intersect -abam ../results/HIF1a_Rep1.bam -b ../data/blacklist/hg38.blacklist.bed.gz -v > ../results/HIF1a_Rep1.rmblacklist.bam

Finally, the next steps deal with marking and removing duplicates. Removing duplicates is usually a good idea, since most likely duplicate reads are originated from PCR, and don’t represent ‘true’ reads.

# collate: group by name
samtools collate ../results/HIF1a_Rep1.rmblacklist.bam -o ../results/HIF1a_Rep1.collate.bam
# fills in mate coordinates and insert size fields
samtools fixmate -m ../results/HIF1a_Rep1.collate.bam ../results/HIF1a_Rep1.fixmate.bam
# sort bam
samtools sort ../results/HIF1a_Rep1.fixmate.bam -o ../results/HIF1a_Rep1.sorted.bam
# mark and remove duplicates
samtools markdup ../results/HIF1a_Rep1.sorted.bam ../results/HIF1a_Rep1.sorted.markdup.bam # with this command we don't remove duplicates
samtools markdup -r ../results/HIF1a_Rep1.sorted.bam ../results/HIF1a_Rep1.sorted.rmdup.bam
# create index
samtools index ../results/HIF1a_Rep1.sorted.rmdup.bam

We can compare the initial number of reads in the fastq file versus the number of reads and fragments after mapping and filtering.

## Function to count the number of reads in a fastq.gz file
function nreadsfastq(){
    zcat ${1} | echo $((`wc -l`/4))
}
nreadsfastq ../data/fastq/HIF1a_Rep1_R1.fastq.gz
nreadsfastq ../data/fastq/HIF1a_Rep1_R2.fastq.gz
121159
121159
samtools flagstat ../results/HIF1a_Rep1.sorted.markdup.bam
234228 + 0 in total (QC-passed reads + QC-failed reads)
0 + 0 secondary
0 + 0 supplementary
41550 + 0 duplicates
234228 + 0 mapped (100.00% : N/A)
234152 + 0 paired in sequencing
117076 + 0 read1
117076 + 0 read2
231884 + 0 properly paired (99.03% : N/A)
234152 + 0 with itself and mate mapped
0 + 0 singletons (0.00% : N/A)
0 + 0 with mate mapped to a different chr
0 + 0 with mate mapped to a different chr (mapQ>=5)
samtools flagstat ../results/HIF1a_Rep1.sorted.rmdup.bam
192678 + 0 in total (QC-passed reads + QC-failed reads)
0 + 0 secondary
0 + 0 supplementary
0 + 0 duplicates
192678 + 0 mapped (100.00% : N/A)
192610 + 0 paired in sequencing
96305 + 0 read1
96305 + 0 read2
190460 + 0 properly paired (98.88% : N/A)
192610 + 0 with itself and mate mapped
0 + 0 singletons (0.00% : N/A)
0 + 0 with mate mapped to a different chr
0 + 0 with mate mapped to a different chr (mapQ>=5)

And that’s it! Now you are familiar with alignment using bowtie2 and SAM/BAM processing.

03 - Exercises

    1. Perform the alignment steps for the HIF2a data. The input fastq files can be found in the data folder:
ls ../data/fastq/
HIF1a_Rep1_R1.fastq.gz
HIF1a_Rep1_R2.fastq.gz
HIF2a_Rep1_R1.fastq.gz
HIF2a_Rep1_R2.fastq.gz
    1. How would you filter for reads that are unmapped and have a mate unmapped? Give it a try using the unfiltered .sam output file from HIF1a. Remember that you can check the SAM FLAGS in here.
    1. How would you run bowtie2 using the end-to-end mode? How do the results compare to the local mode?
sessionInfo()
R version 4.3.1 (2023-06-16)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 20.04.6 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0 
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0

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

time zone: America/New_York
tzcode source: system (glibc)

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

loaded via a namespace (and not attached):
 [1] digest_0.6.33   R6_2.5.1        fastmap_1.1.1   xfun_0.40      
 [5] cachem_1.0.8    knitr_1.43      htmltools_0.5.6 rmarkdown_2.24 
 [9] cli_3.6.1       sass_0.4.7      jquerylib_0.1.4 compiler_4.3.1 
[13] tools_4.3.1     mime_0.12       evaluate_0.21   bslib_0.5.1    
[17] yaml_2.3.7      rlang_1.1.1     jsonlite_1.8.7 
LS0tCnRpdGxlOiAiTWlDTTogMDEgLSBBbGlnbm1lbnQiCmF1dGhvcjogIkFyaWVsIE1hZHJpZ2FsIEFndWlycmUiCmRhdGU6ICIyMDIzLTEwLTI1IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiAKICAgICAgY29sbGFwc2VkOiBmYWxzZQogICAgICBzbW9vdGhfc2Nyb2xsOiBmYWxzZQotLS0KCmBgYHtyLGVjaG89RkFMU0V9Cmh0bWx0b29sczo6aW1nKHNyYyA9IGtuaXRyOjppbWFnZV91cmkoImltYWdlcy9taWNtX2NvbG9yX2xvZ28ucG5nIiksIAogICAgICAgICAgICAgICBhbHQgPSAnbG9nbycsIAogICAgICAgICAgICAgICBzdHlsZSA9ICdwb3NpdGlvbjphYnNvbHV0ZTsgdG9wOjMwcHg7IHJpZ2h0OjA7IHBhZGRpbmc6MTBweDsgbWF4LXdpZHRoOjUwJTsnKQpgYGAKClRoaXMgd29ya3Nob3Agd2lsbCBndWlkZSB5b3UgdGhyb3VnaCB0aGUgYmFzaWNzIG9mIENoSVAtc2VxIGFuYWx5c2lzIHdpdGggaGFuZHMtb24gZXhlcmNpc2VzLiBZb3Ugd2lsbCBsZWFybiBob3cgdG8gcHJvY2VzcyBDaElQLXNlcSBkYXRhOiBwZXJmb3JtIHJlYWQgYWxpZ25tZW50LCBwZWFrIGNhbGxpbmcsIHF1YWxpdHkgY29udHJvbCwgdmlzdWFsaXphdGlvbiB0aHJvdWdoIHRoZSBnZW5vbWUgYnJvd3NlciwgbW90aWYgZmluZGluZyBhbmQgZ2VuZSBzZXQgZW5yaWNobWVudCBhbmFseXNpcy4KCkluIHRoaXMgdHV0b3JpYWwsIHdlIGFyZSBnb2luZyB0byBmb2N1cyBvbiBwZXJmb3JtaW5nIHJlYWQgYWxpZ25tZW50LiAKClRoZSBkYXRhc2V0IHRoYXQgd2Ugd2lsbCBiZSB1c2luZyB0b2RheSBpcyBDaElQLXNlcSBmb3IgW0hJRjFhXShodHRwczovL3d3dy5nZW5lY2FyZHMub3JnL2NnaS1iaW4vY2FyZGRpc3AucGw/Z2VuZT1ISUYxQSkgYW5kIFtISUYyYV0oaHR0cHM6Ly93d3cuZ2VuZWNhcmRzLm9yZy9jZ2ktYmluL2NhcmRkaXNwLnBsP2dlbmU9RVBBUzEpLiBISUYxYSBhbmQgSElGMmEgYXJlIHRoZSB0d28gcHJpbmNpcGFsIGlzb2Zvcm1zIG9mIHRoZSBIeXBveGlhLWluZHVjaWJsZSBmYWN0b3IgKEhJRiksIHdoaWNoIGlzIHRoZSBtYWpvciB0cmFuc2NyaXB0aW9uYWwgcmVndWxhdG9yIG9mIGNlbGx1bGFyIHJlc3BvbnNlcyB0byBoeXBveGlhLiBUaGlzIGRhdGFzZXQgd2FzIGdlbmVyYXRlZCBieSBTbXl0aGllcyAqZXQgYWwqICgyMDE4KSwgYW5kIHlvdSBjYW4gZmluZCB0aGUgcGFwZXIgW2hlcmVdKGh0dHBzOi8vZG9pLm9yZy8xMC4xNTI1Mi9lbWJyLjIwMTg0NjQwMSkuIEJlY2F1c2UgdGhlIG9yaWdpbmFsIGRhdGEgaXMgdmVyeSBiaWcsIHdlIGFyZSBnb2luZyB0byB3b3JrIHdpdGggYSBzdWJzZXQgb2YgdGhlIG9yaWdpbmFsIHJlYWRzIGZyb20gdGhlIHN0dWR5IChyZWFkcyB0aGF0IG1hcCB0byBhIHNlZ21lbnQgb2YgdGhlIGNocm9tb3NvbWUgNikuCgoqKk5vdGUqKjogQWxsIHRoZSBjb2RlIGluIHRoaXMgdHV0b3JpYWwgd2lsbCBiZSBwZXJmb3JtZWQgaW4gdGhlIHRlcm1pbmFsLiAKCkxldCdzIG1vdmUgdG8gdGhlIHNjcmlwdHMgZGlyZWN0b3J5IChvciBzdGF5IGhlcmUgaWYgYWxyZWFkeSB0aGVyZSk6CmBgYHtiYXNoLCBldmFsPUZ9CmNkIGNoaXBfc2VxL3NjcmlwdHMKYGBgCgojIDAxIC0gTWFwcGluZyB3aXRoIGJvd3RpZTIKCllvdSBjYW4gY2hlY2sgdGhlIGhlbHAgb24gaG93IHRvIHVzZSBib3d0aWUyIGJ5IHR5cGluZzoKYGBge2Jhc2h9CmJvd3RpZTIgLS1oZWxwIApgYGAKWW91IGNhbiBhbHNvIGFjY2VzcyB0aGUgYm93dGllMiBtYW51YWwgaW4gW2hlcmVdKGh0dHBzOi8vYm93dGllLWJpby5zb3VyY2Vmb3JnZS5uZXQvYm93dGllMi9tYW51YWwuc2h0bWwpLgoKVGhlIGRhdGEgdGhhdCB3ZSBoYXZlIGlzIHBhaXJlZCBlbmQgZGF0YSwgc28gd2UgaGF2ZSAyIC5mYXN0cSBmaWxlcyBmb3Igb25lIHNhbXBsZS4gVGhleSBsb29rIGxpa2UgdGhpczoKYGBge2Jhc2h9CmxzIC4uL2RhdGEvZmFzdHEKYGBgCgpCb3d0aWUyIHJlcXVpcmVzIGEgZ2Vub21lIGluZGV4IChjb21wcmVzc2VkIGZpbGVzIHRoYXQgc3RvcmUgdGhlIHJlZmVyZW5jZSBzZXF1ZW5jZSkuIEZvciB0aGlzIHRpbWUsIHdlJ3ZlIGFscmVhZHkgZ2VuZXJhdGVkIHRoZSBpbmRleCBhbmQgY2FuIGJlIGZvdW5kIGhlcmU6CmBgYHtiYXNofQpscyAuLi9kYXRhL3JlZmVyZW5jZS9HUkNoMzhfY2hyNgpgYGAKVGhpcyBpbmRleCB3YXMgY3JlYXRlZCBvbmx5IHVzaW5nIHRoZSBodW1hbiBjaHJvbW9zb21lIDYuIFlvdSBjYW4gY3JlYXRlIHlvdXIgb3duIGluZGV4IG9yIGRvd25sb2FkIHByZS1idWlsdCBvbmVzIGZyb20gdGhlIGJvd3RpZTIgcGFnZS4KCkxldCdzIGNyZWF0ZSBhIGRpcmVjdG9yeSB3aGVyZSB0byBzdG9yZSBvdXIgYWxpZ25tZW50CmBgYHtiYXNofQpta2RpciAtcCAuLi9yZXN1bHRzCmBgYApOb3cgd2UgYXJlIHJlYWR5IHRvIHJ1biBib3d0aWUyIQpgYGB7YmFzaH0KYm93dGllMiAtLXZlcnktc2Vuc2l0aXZlLWxvY2FsIC14IC4uL2RhdGEvcmVmZXJlbmNlL0dSQ2gzOF9jaHI2L2hnMzhfY2hyNiAtMSAuLi9kYXRhL2Zhc3RxL0hJRjFhX1JlcDFfUjEuZmFzdHEuZ3ogLTIgLi4vZGF0YS9mYXN0cS9ISUYxYV9SZXAxX1IyLmZhc3RxLmd6IC1TIC4uL3Jlc3VsdHMvSElGMWFfUmVwMS5zYW0KYGBgCkxldCdzIHRha2UgYSBsb29rIGF0IHRoZSBwYXJhbWV0ZXJzIHRoYXQgd2UndmUgdXNlZCBmb3IgcnVubmluZyBib3d0aWUyOgoKKiAtLXZlcnktc2Vuc2l0aXZlLWxvY2FsOiBCb3d0aWUyIGNvbWVzIHdpdGggc29tZSB1c2VmdWwgY29tYmluYXRpb25zIG9mIHBhcmFtZXRlcnMgcGFja2FnZWQgaW50byBzaG9ydGVyICJwcmVzZXQiIHBhcmFtZXRlcnMuIFRoZSBwcmVzZXQgb3B0aW9ucyB0aGF0IGNvbWUgd2l0aCBCb3d0aWUgMiBhcmUgZGVzaWduZWQgdG8gY292ZXIgYSB3aWRlIGFyZWEgb2YgdGhlIHNwZWVkL3NlbnNpdGl2aXR5L2FjY3VyYWN5IHRyYWRlLW9mZiBzcGFjZS4gIkZhc3QiIHByZXNldHMgYXJlIGdlbmVyYWxseSBmYXN0ZXIgYnV0IGxlc3Mgc2Vuc2l0aXZlLCB3aGlsZSB0aGUgInNlbnNpdGl2ZSIgcHJlc2V0cyBhcmUgZ2VuZXJhbGx5IHNsb3dlciBidXQgbW9yZSBzZW5zaXRpdmUgYW5kIGFjY3VyYXRlLiBUaGUgbG9jYWwgcGFydCBtZWFucyB0aGF0IHdlIGFyZSBkb2luZyAnbG9jYWwnIGFsaWdubWVudCAocmVtZW1iZXIgdGhhdCB0aGUgZGVmYXVsdCBpbiBCb3d0aWUyIGlzIHRoZSBlbmQtdG8tZW5kIGFsaWdubWVudCkuIAoqIC0teDogcGF0aCB0byB0aGUgaW5kZXgKKiAtMTogcGF0aCB0byBmYXN0cTEgCiogLTI6IHBhdGggdG8gZmFzdHEyCiogLVM6IHBhdGggdG8gdGhlIG91dHB1dCBTQU0gZmlsZQoKIyAwMiAtIEZpbHRlcmluZyBTQU0vQkFNCgpGb3IgdGhlIGZvbGxvd2luZyBzdGVwcywgd2Ugd2lsbCBiZSB1c2luZyBzYW10b29scy4gVGhlIG1hbnVhbCBjYW4gYmUgZm91bmQgW2hlcmVdKGh0dHA6Ly93d3cuaHRzbGliLm9yZy9kb2Mvc2FtdG9vbHMuaHRtbCkuIFNhbXRvb2xzIGlzIGEgY29tcGVuZGl1bSBvZiB2YXJpb3VzIHRvb2xzOgpgYGB7YmFzaH0Kc2FtdG9vbHMgLS1oZWxwCmBgYApXZSBjYW4gY291bnQgdGhlIG51bWJlciBvZiByZWFkcyB0aGF0IGFyZSBpbiBvdXIgU0FNCmBgYHtiYXNofQpzYW10b29scyB2aWV3IC1jIC4uL3Jlc3VsdHMvSElGMWFfUmVwMS5zYW0KYGBgCiogLWM6IEluc3RlYWQgb2YgcHJpbnRpbmcgdGhlIGFsaWdubWVudHMsIG9ubHkgY291bnQgdGhlbSBhbmQgcHJpbnQgdGhlIHRvdGFsIG51bWJlcgoKV2UgbmVlZCB0byBjb252ZXJ0IHRoZSBTQU0gZmlsZSBpbnRvIGEgQkFNIGZpbGUuIEluIHRoaXMgc2FtZSBzdGVwLCB3ZSB3aWxsIHBlcmZvcm0gc29tZSBmaWx0ZXJpbmcgb2YgdGhlIGFsaWdubWVudHM6CmBgYHtiYXNofQpzYW10b29scyB2aWV3IC1oIC1GIDE4MDQgLXEgMzAgLWIgLi4vcmVzdWx0cy9ISUYxYV9SZXAxLnNhbSAtbyAuLi9yZXN1bHRzL0hJRjFhX1JlcDEuYmFtCmBgYAoqIC1oOiBpbmNsdWRlIGhlYWRlciBpbiB0aGUgb3V0cHV0CiogLUY6IEV4Y2x1ZGUgYWxpZ25tZW50cyB3aXRoIGEgZ2l2ZW4gRkxBRyBmaWVsZC4gVGhlIDE4MDQgRkxBRyBleGNsdWRlcyB0aGUgZm9sbG93aW5nIHJlYWRzOiByZWFkIHVubWFwcGVkLCBtYXRlIHVubWFwcGVkLCBub3QgcHJpbWFyeSBhbGlnbm1lbnQsIHJlYWQgcXVhbGl0eSBsb3csIFBDUiAvIG9wdGljYWwgZHVwbGljYXRlLiBZb3UgY2FuIGNoZWNrIHRoZSBtZWFuaW5nIG9mIHRoZSBTQU0gRkxBR1MgaW4gW2hlcmVdKGh0dHBzOi8vYnJvYWRpbnN0aXR1dGUuZ2l0aHViLmlvL3BpY2FyZC9leHBsYWluLWZsYWdzLmh0bWwpLiBNb3JlIGluZm9ybWF0aW9uIGFib3V0IHNhbXRvb2xzIGZsYWdzIGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cHM6Ly9zYW10b29scy5naXRodWIuaW8vaHRzLXNwZWNzL1NBTXYxLnBkZikuCiogLXE6IFNraXAgYWxpZ25tZW50cyB3aXRoIE1BUFEgc21hbGxlciB0aGFuIHRoZSBnaXZlbiB2YWx1ZS4gVGhlIE1BUFEgaXMgYSBub24tbmVnYXRpdmUgaW50ZWdlciBRID0gLTEwIGxvZzEwIHAsIHdoZXJlIHAgaXMgYW4gZXN0aW1hdGUgb2YgdGhlIHByb2JhYmlsaXR5IHRoYXQgdGhlIGFsaWdubWVudCBkb2VzIG5vdCBjb3JyZXNwb25kIHRvIHRoZSByZWFkJ3MgdHJ1ZSBwb2ludCBvZiBvcmlnaW4uIFVzdWFsbHksIGEgZ29vZCB2YWx1ZSBpcyBiZXR3ZWVuIDEwLTMwLiAKKiAtYjogT3V0cHV0IGluIHRoZSBCQU0gZm9ybWF0LgoqIC1vOiBPdXRwdXQgdG8gZmlsZQoKV2UgY2FuIGFsc28gcmVtb3ZlIHJlYWRzIHRoYXQgbWFwIGJsYWNrbGlzdGVkIHJlZ2lvbnMgYnkgW0VOQ09ERV0oaHR0cHM6Ly9naXRodWIuY29tL0JveWxlLUxhYi9CbGFja2xpc3QpIHVzaW5nIGJlZHRvb2xzLiAKCmBgYHtiYXNofQpiZWR0b29scyBpbnRlcnNlY3QgLWFiYW0gLi4vcmVzdWx0cy9ISUYxYV9SZXAxLmJhbSAtYiAuLi9kYXRhL2JsYWNrbGlzdC9oZzM4LmJsYWNrbGlzdC5iZWQuZ3ogLXYgPiAuLi9yZXN1bHRzL0hJRjFhX1JlcDEucm1ibGFja2xpc3QuYmFtCmBgYAoKRmluYWxseSwgdGhlIG5leHQgc3RlcHMgZGVhbCB3aXRoIG1hcmtpbmcgYW5kIHJlbW92aW5nIGR1cGxpY2F0ZXMuIFJlbW92aW5nIGR1cGxpY2F0ZXMgaXMgdXN1YWxseSBhIGdvb2QgaWRlYSwgc2luY2UgbW9zdCBsaWtlbHkgZHVwbGljYXRlIHJlYWRzIGFyZSBvcmlnaW5hdGVkIGZyb20gUENSLCBhbmQgZG9uJ3QgcmVwcmVzZW50ICd0cnVlJyByZWFkcy4gCgpgYGB7YmFzaH0KIyBjb2xsYXRlOiBncm91cCBieSBuYW1lCnNhbXRvb2xzIGNvbGxhdGUgLi4vcmVzdWx0cy9ISUYxYV9SZXAxLnJtYmxhY2tsaXN0LmJhbSAtbyAuLi9yZXN1bHRzL0hJRjFhX1JlcDEuY29sbGF0ZS5iYW0KIyBmaWxscyBpbiBtYXRlIGNvb3JkaW5hdGVzIGFuZCBpbnNlcnQgc2l6ZSBmaWVsZHMKc2FtdG9vbHMgZml4bWF0ZSAtbSAuLi9yZXN1bHRzL0hJRjFhX1JlcDEuY29sbGF0ZS5iYW0gLi4vcmVzdWx0cy9ISUYxYV9SZXAxLmZpeG1hdGUuYmFtCiMgc29ydCBiYW0Kc2FtdG9vbHMgc29ydCAuLi9yZXN1bHRzL0hJRjFhX1JlcDEuZml4bWF0ZS5iYW0gLW8gLi4vcmVzdWx0cy9ISUYxYV9SZXAxLnNvcnRlZC5iYW0KIyBtYXJrIGFuZCByZW1vdmUgZHVwbGljYXRlcwpzYW10b29scyBtYXJrZHVwIC4uL3Jlc3VsdHMvSElGMWFfUmVwMS5zb3J0ZWQuYmFtIC4uL3Jlc3VsdHMvSElGMWFfUmVwMS5zb3J0ZWQubWFya2R1cC5iYW0gIyB3aXRoIHRoaXMgY29tbWFuZCB3ZSBkb24ndCByZW1vdmUgZHVwbGljYXRlcwpzYW10b29scyBtYXJrZHVwIC1yIC4uL3Jlc3VsdHMvSElGMWFfUmVwMS5zb3J0ZWQuYmFtIC4uL3Jlc3VsdHMvSElGMWFfUmVwMS5zb3J0ZWQucm1kdXAuYmFtCiMgY3JlYXRlIGluZGV4CnNhbXRvb2xzIGluZGV4IC4uL3Jlc3VsdHMvSElGMWFfUmVwMS5zb3J0ZWQucm1kdXAuYmFtCmBgYAoKV2UgY2FuIGNvbXBhcmUgdGhlIGluaXRpYWwgbnVtYmVyIG9mIHJlYWRzIGluIHRoZSBmYXN0cSBmaWxlIHZlcnN1cyB0aGUgbnVtYmVyIG9mIHJlYWRzIGFuZCBmcmFnbWVudHMgYWZ0ZXIgbWFwcGluZyBhbmQgZmlsdGVyaW5nLgpgYGB7YmFzaH0KIyMgRnVuY3Rpb24gdG8gY291bnQgdGhlIG51bWJlciBvZiByZWFkcyBpbiBhIGZhc3RxLmd6IGZpbGUKZnVuY3Rpb24gbnJlYWRzZmFzdHEoKXsKICAgIHpjYXQgJHsxfSB8IGVjaG8gJCgoYHdjIC1sYC80KSkKfQpucmVhZHNmYXN0cSAuLi9kYXRhL2Zhc3RxL0hJRjFhX1JlcDFfUjEuZmFzdHEuZ3oKbnJlYWRzZmFzdHEgLi4vZGF0YS9mYXN0cS9ISUYxYV9SZXAxX1IyLmZhc3RxLmd6CmBgYApgYGB7YmFzaH0Kc2FtdG9vbHMgZmxhZ3N0YXQgLi4vcmVzdWx0cy9ISUYxYV9SZXAxLnNvcnRlZC5tYXJrZHVwLmJhbQpgYGAKYGBge2Jhc2h9CnNhbXRvb2xzIGZsYWdzdGF0IC4uL3Jlc3VsdHMvSElGMWFfUmVwMS5zb3J0ZWQucm1kdXAuYmFtCmBgYAoKQW5kIHRoYXQncyBpdCEgTm93IHlvdSBhcmUgZmFtaWxpYXIgd2l0aCBhbGlnbm1lbnQgdXNpbmcgYm93dGllMiBhbmQgU0FNL0JBTSBwcm9jZXNzaW5nLgoKIyAwMyAtIEV4ZXJjaXNlcwoKKiAxLiBQZXJmb3JtIHRoZSBhbGlnbm1lbnQgc3RlcHMgZm9yIHRoZSBISUYyYSBkYXRhLiBUaGUgaW5wdXQgZmFzdHEgZmlsZXMgY2FuIGJlIGZvdW5kIGluIHRoZSBkYXRhIGZvbGRlcjoKYGBge2Jhc2h9CmxzIC4uL2RhdGEvZmFzdHEvCmBgYAoKKiAyLiBIb3cgd291bGQgeW91IGZpbHRlciBmb3IgcmVhZHMgdGhhdCBhcmUgdW5tYXBwZWQgYW5kIGhhdmUgYSBtYXRlIHVubWFwcGVkPyBHaXZlIGl0IGEgdHJ5IHVzaW5nIHRoZSB1bmZpbHRlcmVkIC5zYW0gb3V0cHV0IGZpbGUgZnJvbSBISUYxYS4gUmVtZW1iZXIgdGhhdCB5b3UgY2FuIGNoZWNrIHRoZSBTQU0gRkxBR1MgaW4gW2hlcmVdKGh0dHBzOi8vYnJvYWRpbnN0aXR1dGUuZ2l0aHViLmlvL3BpY2FyZC9leHBsYWluLWZsYWdzLmh0bWwpLgoKKiAzLiBIb3cgd291bGQgeW91IHJ1biBib3d0aWUyIHVzaW5nIHRoZSBlbmQtdG8tZW5kIG1vZGU/IEhvdyBkbyB0aGUgcmVzdWx0cyBjb21wYXJlIHRvIHRoZSBsb2NhbCBtb2RlPwoKIApgYGB7cn0Kc2Vzc2lvbkluZm8oKQpgYGAK