This is a quick walkthrough covering key PAGODA2 usage patterns to date.

We will analyze an 8k PBMC dataset from 10x as an example (starting with unfiltered read count matrices).

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Cmd+Shift+Enter.

First we’ll load the library

library(pagoda2)

Now let’s load the 10x matrices:

cd <- read.10x.matrices(list(PBMC8K='pbmc8k/raw_gene_bc_matrices/GRCh38'))[[1]]
reading 1 dataset(s) . done
str(cd)
Formal class 'dgCMatrix' [package "Matrix"] with 6 slots
  ..@ i       : int [1:17171166] 1614 21625 110 153 231 484 548 550 633 673 ...
  ..@ p       : int [1:737281] 0 2 2 2 116 120 120 125 126 126 ...
  ..@ Dim     : int [1:2] 33694 737280
  ..@ Dimnames:List of 2
  .. ..$ : chr [1:33694] "RP11-34P13.3" "FAM138A" "OR4F5" "RP11-34P13.7" ...
  .. ..$ : chr [1:737280] "PBMC8K_AAACCTGAGAAACCAT-1" "PBMC8K_AAACCTGAGAAACCGC-1" "PBMC8K_AAACCTGAGAAACCTA-1" "PBMC8K_AAACCTGAGAAACGAG-1" ...
  ..@ x       : num [1:17171166] 1 1 1 1 1 2 1 1 1 1 ...
  ..@ factors : list()

Look at the summary counts across genes and across cells:

par(mfrow=c(1,2), mar = c(3.5,3.5,2.0,0.5), mgp = c(2,0.65,0), cex = 1.0)
hist(log10(colSums(cd)+1),main='molecules per cell',col='wheat',xlab='log10[ molecules per cell]')
hist(log10(rowSums(cd)+1),main='molecules per gene',col='wheat',xlab='log10[ molecules per gene]')

This is unfiltered data, so most of the barcodes are empty/background. Let’s require at least 500 molecules per cell, and use our quick umi-gene relationship function to filter the cells.

counts <- gene.vs.molecule.cell.filter(cd,min.cell.size=500)

str(counts)
Formal class 'dgCMatrix' [package "Matrix"] with 6 slots
  ..@ i       : int [1:11965872] 33 45 59 153 335 375 408 440 484 495 ...
  ..@ p       : int [1:8787] 0 871 1677 2993 3891 5417 6912 8165 9598 11230 ...
  ..@ Dim     : int [1:2] 33694 8786
  ..@ Dimnames:List of 2
  .. ..$ : chr [1:33694] "RP11-34P13.3" "FAM138A" "OR4F5" "RP11-34P13.7" ...
  .. ..$ : chr [1:8786] "PBMC8K_AAACCTGAGCATCATC-1" "PBMC8K_AAACCTGAGCTAACTC-1" "PBMC8K_AAACCTGAGCTAGTGG-1" "PBMC8K_AAACCTGCACATTAGC-1" ...
  ..@ x       : num [1:11965872] 1 1 1 5 1 1 1 1 18 1 ...
  ..@ factors : list()

We can also looka at the number of molecules per gene, and omit low-expressed genes to save computational time:

hist(log10(rowSums(counts)+1),main='Molecules per gene',xlab='molecules (log10)',col='wheat')
abline(v=1,lty=2,col=2)

counts <- counts[rowSums(counts)>=10,]
str(counts)
Formal class 'dgCMatrix' [package "Matrix"] with 6 slots
  ..@ i       : int [1:11947057] 11 17 30 84 174 192 207 222 247 257 ...
  ..@ p       : int [1:8787] 0 869 1675 2991 3889 5412 6907 8158 9590 11219 ...
  ..@ Dim     : int [1:2] 15556 8786
  ..@ Dimnames:List of 2
  .. ..$ : chr [1:15556] "RP11-34P13.7" "FO538757.2" "AP006222.2" "RP4-669L17.10" ...
  .. ..$ : chr [1:8786] "PBMC8K_AAACCTGAGCATCATC-1" "PBMC8K_AAACCTGAGCTAACTC-1" "PBMC8K_AAACCTGAGCTAGTGG-1" "PBMC8K_AAACCTGCACATTAGC-1" ...
  ..@ x       : num [1:11947057] 1 1 1 5 1 1 1 1 18 1 ...
  ..@ factors : list()

Now we have a clean/lean count matrix and are ready to start analysis. First we’ll create pagoda2 object that will maintain all of the results. It will also provide handles for running all operations on the data. Use of log scale is recommended, as it makes some statistics smoother.

r <- Pagoda2$new(counts,log.scale=TRUE)
Error in setCountMatrix(x, min.cells.per.gene = min.cells.per.gene, trim = trim,  : 
  duplicate gene names are not allowed - please reduce

Yes, we need to make gene names unique:

rownames(counts) <- make.unique(rownames(counts))
r <- Pagoda2$new(counts,log.scale=TRUE, n.cores=20)
8786 cells, 13004 genes; normalizing ... using plain model winsorizing ... log scale ... done.

Note, that above we’ve also specified the default number of processing cores that will be used to speed up some of the analysis steps below. Please match it to your system.

Next, we’ll adjust the variance, to normalize the extent to which genes with (very) different expression magnitudes will contribute to the downstream anlaysis:

r$adjustVariance(plot=T,gam.k=10)
calculating variance fit ... using gam 
Loading required package: mgcv
Loading required package: nlme
This is mgcv 1.8-18. For overview type 'help("mgcv-package")'.
538 overdispersed genes ... 538 persisting ... done.

There are many alternative ways of proceeding with the downstream analysis. Below we’ll use the simplest, default scenario, where we first reduce the dataset dimensions by running PCA, and then move into k-nearest neighbor graph space for clustering and visualization calculations. First, the PCA reduction:

r$calculatePcaReduction(nPcs=50,n.odgenes=3e3)
Loading required package: irlba

The next few steps will make kNN graph, find clusters and generate a quick largeVis embedding to visualize the subpopulations:

r$makeKnnGraph(k=40,type='PCA',center=T,distance='cosine');
Loading required package: igraph

Attaching package: ‘igraph’

The following objects are masked from ‘package:stats’:

    decompose, spectrum

The following object is masked from ‘package:base’:

    union
creating space of type angular done
adding data ... done
building index ... done
querying ... done
r$getKnnClusters(method=infomap.community,type='PCA')
M <- 30; r$getEmbedding(type='PCA',M=M,perplexity=30,gamma=1/M,alpha=1)
Estimating embeddings.
0%   10   20   30   40   50   60   70   80   90   100%
|----|----|----|----|----|----|----|----|----|----|
**************************************************|

Now we can visualize the embedding using the determined clusters:

r$plotEmbedding(type='PCA',show.legend=F,mark.clusters=T,min.group.size=50,shuffle.colors=F,mark.cluster.cex=1,alpha=0.1,main='clusters (largeVis)')

Alternatively, we can generate tSNE embedding (takes longer)

r$getEmbedding(type='PCA',embeddingType='tSNE',perplexity=50,verbose=F)
Loading required package: Rtsne
calculating distance ... pearson ...running tSNE using 20 cores:
r$plotEmbedding(type='PCA',embeddingType='tSNE',show.legend=F,mark.clusters=T,min.group.size=1,shuffle.colors=F,mark.cluster.cex=1,alpha=0.1,main='clusters (tSNE)')

We can use the same plotEmbedding() function to show all kinds of other values. For instance, let’s look at depth, or an expresson pattern of a gene:

str(r$depth)
 Named num [1:8786] 2376 1660 4517 2779 4656 ...
 - attr(*, "names")= chr [1:8786] "PBMC8K_AAACCTGAGCATCATC-1" "PBMC8K_AAACCTGAGCTAACTC-1" "PBMC8K_AAACCTGAGCTAGTGG-1" "PBMC8K_AAACCTGCACATTAGC-1" ...
par(mfrow=c(1,2))
r$plotEmbedding(type='PCA',embeddingType='tSNE',colors=r$depth,shuffle.colors=F,mark.cluster.cex=1,alpha=0.1,main='depth')
treating colors as a gradient with zlim: 1440 8903.75 
gene <-"LYZ"
r$plotEmbedding(type='PCA',embeddingType='tSNE',colors=r$counts[,gene],shuffle.colors=F,mark.cluster.cex=1,alpha=0.1,main=gene)
treating colors as a gradient with zlim: 0 2.879075 

We can generate multiple potential clusterings, with different names. Here we’ll use multilevel clustering:

r$getKnnClusters(method=multilevel.community,type='PCA',name='multilevel')
str(r$clusters)
List of 1
 $ PCA:List of 2
  ..$ community : Factor w/ 36 levels "1","2","3","4",..: 27 2 5 3 6 2 14 3 28 3 ...
  .. ..- attr(*, "names")= chr [1:8786] "PBMC8K_AAACCTGAGCATCATC-1" "PBMC8K_AAACCTGAGCTAACTC-1" "PBMC8K_AAACCTGAGCTAGTGG-1" "PBMC8K_AAACCTGCACATTAGC-1" ...
  ..$ multilevel: Factor w/ 16 levels "1","2","3","4",..: 6 9 4 1 9 9 15 1 9 1 ...
  .. ..- attr(*, "names")= chr [1:8786] "PBMC8K_AAACCTGAGCATCATC-1" "PBMC8K_AAACCTGAGCTAACTC-1" "PBMC8K_AAACCTGAGCTAGTGG-1" "PBMC8K_AAACCTGCACATTAGC-1" ...

Compare with infomap:

par(mfrow=c(1,2))
r$plotEmbedding(type='PCA',embeddingType='tSNE',groups=r$clusters$PCA$community,show.legend=F,mark.clusters=T,min.group.size=1,shuffle.colors=F,mark.cluster.cex=1,alpha=0.1,main='infomap clusters (tSNE)')
using provided groups as a factor
r$plotEmbedding(type='PCA',embeddingType='tSNE',clusterType='multilevel',show.legend=F,mark.clusters=T,min.group.size=1,shuffle.colors=F,mark.cluster.cex=1,alpha=0.1,main='multlevel clusters (tSNE)')

Run differential expression on the infomap clusters:

r$getDifferentialGenes(type='PCA',verbose=T,clusterType='community')
running differential expression with  36  clusters ... adjusting p-values ... done.

Visualize top genes:

names(r$diffgenes)
[1] "PCA"
de <- r$diffgenes$PCA[[1]][['4']];
r$plotGeneHeatmap(genes=rownames(de)[1:15],groups=r$clusters$PCA[[1]])

Spot-check a gene

gene <-"IGHM"
r$plotEmbedding(type='PCA',embeddingType='tSNE',colors=r$counts[,gene],shuffle.colors=F,mark.cluster.cex=1,alpha=0.1,main=gene)
treating colors as a gradient with zlim: 0 1.078653 

Pathway overdispersion analysis (a la PAGODA1)

First, build GO->gene environment:

suppressMessages(library(org.Hs.eg.db))
# translate gene names to ids
ids <- unlist(lapply(mget(colnames(r$counts),org.Hs.egALIAS2EG,ifnotfound=NA),function(x) x[1]))
# reverse map
rids <- names(ids); names(rids) <- ids;
# list all the ids per GO category
go.env <- list2env(eapply(org.Hs.egGO2ALLEGS,function(x) as.character(na.omit(rids[x]))))

Now run overdispersion anlaysis

r$testPathwayOverdispersion(go.env,verbose=T,correlation.distance.threshold=0.95,recalculate.pca=F,top.aspects=15)

We’ll use hierarchical differential expression results instead:

r$getHierarchicalDiffExpressionAspects(type='PCA',clusterName='community',z.threshold=3)
using community  clustering for PCA space
1 function calls resulted in an error

We’ll make an app with that, ordering the “differential expression aspects” explicitly (otherwise if row clustering is omitted they’ll be clustered by similarity)

app <- p2.make.pagoda1.app(r,inner.clustering=TRUE,embeddingType='tSNE',clusterType='multilevel',min.group.size=50,row.clustering=list(order=rev(1:nrow(r$misc$pathwayOD$xv))))
Loading required package: fastcluster

Attaching package: ‘fastcluster’

The following object is masked from ‘package:stats’:

    hclust

Show app:

show.app(app,'pbmc',browse=T)

Alternatively, we can create a more advanced p2 app, which can be saved as a standalone binary file and viewed from the browser either by accessing that binary file on the local drive, or over regular HTTP (in other words, without requiring a server or an R session).

First, compile gene sets and metadata that we want to make available in the offline web app:

library(GO.db)
termDescriptions <- Term(GOTERM[names(go.env)]); # saves a good minute or so compared to individual lookups
sn <- function(x) { names(x) <- x; x}
geneSets <- lapply(sn(names(go.env)),function(x) {
  list(properties=list(locked=T,genesetname=x,shortdescription=as.character(termDescriptions[x])),genes=c(go.env[[x]]))
})

# Make metadata
additionalMetadata <- list(multilevel = p2.metadata.from.factor(r$clusters$PCA$multilevel, displayname = 'Multilevel', s = 0.8))

Now generate the web app and the binary output.

# Generate the web application
# dendrogramCellGroups specifies the clustering that will be used to generate the main dendrogram
p2app <- make.p2.app(r, dendrogramCellGroups = r$clusters$PCA$community, additionalMetadata = additionalMetadata, geneSets = geneSets);
# Optional showing of app
#show.app(p2w, name='newPagoda')
# Save serialised web object, RDS app and session image
p2app$serializeToStaticFast(binary.filename = 'pbmc8k.bin',verbose=T)
Writing... matsparse
        p array size: 13005 [First entry value: 0]
        i array size: 11901901 [First entry value: 3]
        x array size: 11901901 [First entry value: 0.86584]
    Sparse matrix header information
        dim1=8786
        dim2=13004
        pStartOffset=32
        iStartOffset=52052
        xStartOffset=47659656
        dimnames1StartOffset=95267260
        dimnames2StartOffset=95513270
        dimnames2EndOffset=95630240
Writing... mataspect
        p array size: 34 [First entry value: 0]
        i array size: 52132 [First entry value: 0]
        x array size: 52132 [First entry value: -19.18]
    Sparse matrix header information
        dim1=8786
        dim2=33
        pStartOffset=32
        iStartOffset=168
        xStartOffset=208696
        dimnames1StartOffset=417224
        dimnames2StartOffset=663234
        dimnames2EndOffset=663923
Writing... sparseMatrixTransp
        p array size: 8787 [First entry value: 0]
        i array size: 11901901 [First entry value: 8]
        x array size: 11901901 [First entry value: 0.351273]
    Sparse matrix header information
        dim1=13004
        dim2=8786
        pStartOffset=32
        iStartOffset=35180
        xStartOffset=47642784
        dimnames1StartOffset=95250388
        dimnames2StartOffset=95367358
        dimnames2EndOffset=95613368
Making File from payload...
    File format information
        Index entry size is 140 bytes
        File header size is 48 bytes
    Preparing header...
    Total index size is: 1960 bytes
    Constructing index...
        Writing entry 0 of size 1 blocks (or 32768 bytes)
        Writing entry 1 of size 25 blocks (or 819200 bytes)
        Writing entry 2 of size 8 blocks (or 262144 bytes)
        Writing entry 3 of size 30 blocks (or 983040 bytes)
        Writing entry 4 of size 1 blocks (or 32768 bytes)
        Writing entry 5 of size 1 blocks (or 32768 bytes)
        Writing entry 6 of size 1 blocks (or 32768 bytes)
        Writing entry 7 of size 67 blocks (or 2195456 bytes)
        Writing entry 8 of size 536 blocks (or 17563648 bytes)
        Writing entry 9 of size 14 blocks (or 458752 bytes)
        Writing entry 10 of size 14 blocks (or 458752 bytes)
        Writing entry 11 of size 2919 blocks (or 95649792 bytes)
        Writing entry 12 of size 21 blocks (or 688128 bytes)
        Writing entry 13 of size 2918 blocks (or 95617024 bytes)
Free up entry payloads
NULL

Now the pbmc8k.bin file can be viewed locally using http://pklab.med.harvard.edu/nikolas/pagoda2/frontend/current/pagodaLocal/index.html (which will ask you to select a file), or by placing the .bin file into some HTTP-accessible location and using a URL like this, where ‘fileURL’ argument would specify the location at which the .bin file can be accessed: http://pklab.med.harvard.edu/nikolas/pagoda2/frontend/current/pagodaURL/index.html?fileURL=http://pklab.med.harvard.edu/nikolas/pagoda2/staticDemo/pagodaPublicDemo.bin

The index.html file (and associated scripts) are avaialble in the “inst” folder of the pagoda2 package, in case you want to set up your own copy.

Here’s a view of the binary file that was produced by this tutorial: http://pklab.med.harvard.edu/peterk/p2/r/index.html?fileURL=http://pklab.med.harvard.edu/peterk/p2/pbmc8k.bin

LS0tCnRpdGxlOiAiUEFHT0RBMiB3YWxrdGhyb3VnaCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBpcyBhIHF1aWNrIHdhbGt0aHJvdWdoIGNvdmVyaW5nIGtleSBbUEFHT0RBMl0oaHR0cDovL2dpdGh1Yi5jb20vaG1zLWRibWkvcGFnb2RhMi8pIHVzYWdlIHBhdHRlcm5zIHRvIGRhdGUuCgpXZSB3aWxsIGFuYWx5emUgYW4gWzhrIFBCTUNdKGh0dHBzOi8vc3VwcG9ydC4xMHhnZW5vbWljcy5jb20vc2luZ2xlLWNlbGwtZ2VuZS1leHByZXNzaW9uL2RhdGFzZXRzL3BibWM4aykgZGF0YXNldCBmcm9tIDEweCBhcyBhbiBleGFtcGxlIChzdGFydGluZyB3aXRoIHVuZmlsdGVyZWQgcmVhZCBjb3VudCBtYXRyaWNlcykuCgpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ21kK1NoaWZ0K0VudGVyKi4gCgpGaXJzdCB3ZSdsbCBsb2FkIHRoZSBsaWJyYXJ5CmBgYHtyIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHBhZ29kYTIpCmBgYAoKCk5vdyBsZXQncyBsb2FkIHRoZSAxMHggbWF0cmljZXM6CmBgYHtyfQpjZCA8LSByZWFkLjEweC5tYXRyaWNlcyhsaXN0KFBCTUM4Sz0ncGJtYzhrL3Jhd19nZW5lX2JjX21hdHJpY2VzL0dSQ2gzOCcpKVtbMV1dCnN0cihjZCkKYGBgCgpMb29rIGF0IHRoZSBzdW1tYXJ5IGNvdW50cyBhY3Jvc3MgZ2VuZXMgYW5kIGFjcm9zcyBjZWxsczoKYGBge3J9CnBhcihtZnJvdz1jKDEsMiksIG1hciA9IGMoMy41LDMuNSwyLjAsMC41KSwgbWdwID0gYygyLDAuNjUsMCksIGNleCA9IDEuMCkKaGlzdChsb2cxMChjb2xTdW1zKGNkKSsxKSxtYWluPSdtb2xlY3VsZXMgcGVyIGNlbGwnLGNvbD0nd2hlYXQnLHhsYWI9J2xvZzEwWyBtb2xlY3VsZXMgcGVyIGNlbGxdJykKaGlzdChsb2cxMChyb3dTdW1zKGNkKSsxKSxtYWluPSdtb2xlY3VsZXMgcGVyIGdlbmUnLGNvbD0nd2hlYXQnLHhsYWI9J2xvZzEwWyBtb2xlY3VsZXMgcGVyIGdlbmVdJykKYGBgCgpUaGlzIGlzIHVuZmlsdGVyZWQgZGF0YSwgc28gbW9zdCBvZiB0aGUgYmFyY29kZXMgYXJlIGVtcHR5L2JhY2tncm91bmQuIExldCdzIHJlcXVpcmUgYXQgbGVhc3QgNTAwIG1vbGVjdWxlcyBwZXIgY2VsbCwgYW5kIHVzZSBvdXIgcXVpY2sgdW1pLWdlbmUgcmVsYXRpb25zaGlwIGZ1bmN0aW9uIHRvIGZpbHRlciB0aGUgY2VsbHMuCmBgYHtyfQpjb3VudHMgPC0gZ2VuZS52cy5tb2xlY3VsZS5jZWxsLmZpbHRlcihjZCxtaW4uY2VsbC5zaXplPTUwMCkKc3RyKGNvdW50cykKYGBgCgpXZSBjYW4gYWxzbyBsb29rYSBhdCB0aGUgbnVtYmVyIG9mIG1vbGVjdWxlcyBwZXIgZ2VuZSwgYW5kIG9taXQgbG93LWV4cHJlc3NlZCBnZW5lcyB0byBzYXZlIGNvbXB1dGF0aW9uYWwgdGltZToKYGBge3J9Cmhpc3QobG9nMTAocm93U3Vtcyhjb3VudHMpKzEpLG1haW49J01vbGVjdWxlcyBwZXIgZ2VuZScseGxhYj0nbW9sZWN1bGVzIChsb2cxMCknLGNvbD0nd2hlYXQnKQphYmxpbmUodj0xLGx0eT0yLGNvbD0yKQpjb3VudHMgPC0gY291bnRzW3Jvd1N1bXMoY291bnRzKT49MTAsXQpzdHIoY291bnRzKQpgYGAKCk5vdyB3ZSBoYXZlIGEgY2xlYW4vbGVhbiBjb3VudCBtYXRyaXggYW5kIGFyZSByZWFkeSB0byBzdGFydCBhbmFseXNpcy4gRmlyc3Qgd2UnbGwgY3JlYXRlIHBhZ29kYTIgb2JqZWN0IHRoYXQgd2lsbCBtYWludGFpbiBhbGwgb2YgdGhlIHJlc3VsdHMuIEl0IHdpbGwgYWxzbyBwcm92aWRlIGhhbmRsZXMgZm9yIHJ1bm5pbmcgYWxsIG9wZXJhdGlvbnMgb24gdGhlIGRhdGEuIFVzZSBvZiBsb2cgc2NhbGUgaXMgcmVjb21tZW5kZWQsIGFzIGl0IG1ha2VzIHNvbWUgc3RhdGlzdGljcyBzbW9vdGhlci4KCmBgYHtyIGVycm9yPVRSVUV9CnIgPC0gUGFnb2RhMiRuZXcoY291bnRzLGxvZy5zY2FsZT1UUlVFKQpgYGAKClllcywgd2UgbmVlZCB0byBtYWtlIGdlbmUgbmFtZXMgdW5pcXVlOgpgYGB7cn0Kcm93bmFtZXMoY291bnRzKSA8LSBtYWtlLnVuaXF1ZShyb3duYW1lcyhjb3VudHMpKQpyIDwtIFBhZ29kYTIkbmV3KGNvdW50cyxsb2cuc2NhbGU9VFJVRSwgbi5jb3Jlcz0yMCkKYGBgCk5vdGUsIHRoYXQgYWJvdmUgd2UndmUgYWxzbyBzcGVjaWZpZWQgdGhlIGRlZmF1bHQgbnVtYmVyIG9mIHByb2Nlc3NpbmcgY29yZXMgdGhhdCB3aWxsIGJlIHVzZWQgdG8gc3BlZWQgdXAgc29tZSBvZiB0aGUgYW5hbHlzaXMgc3RlcHMgYmVsb3cuIFBsZWFzZSBtYXRjaCBpdCB0byB5b3VyIHN5c3RlbS4KCk5leHQsIHdlJ2xsIGFkanVzdCB0aGUgdmFyaWFuY2UsIHRvIG5vcm1hbGl6ZSB0aGUgZXh0ZW50IHRvIHdoaWNoIGdlbmVzIHdpdGggKHZlcnkpIGRpZmZlcmVudCBleHByZXNzaW9uIG1hZ25pdHVkZXMgd2lsbCBjb250cmlidXRlIHRvIHRoZSBkb3duc3RyZWFtIGFubGF5c2lzOgpgYGB7cn0KciRhZGp1c3RWYXJpYW5jZShwbG90PVQsZ2FtLms9MTApCmBgYAoKVGhlcmUgYXJlIG1hbnkgYWx0ZXJuYXRpdmUgd2F5cyBvZiBwcm9jZWVkaW5nIHdpdGggdGhlIGRvd25zdHJlYW0gYW5hbHlzaXMuIEJlbG93IHdlJ2xsIHVzZSB0aGUgc2ltcGxlc3QsIGRlZmF1bHQgc2NlbmFyaW8sIHdoZXJlIHdlIGZpcnN0IHJlZHVjZSB0aGUgZGF0YXNldCBkaW1lbnNpb25zIGJ5IHJ1bm5pbmcgUENBLCBhbmQgdGhlbiBtb3ZlIGludG8gay1uZWFyZXN0IG5laWdoYm9yIGdyYXBoIHNwYWNlIGZvciBjbHVzdGVyaW5nIGFuZCB2aXN1YWxpemF0aW9uIGNhbGN1bGF0aW9ucy4gRmlyc3QsIHRoZSBQQ0EgcmVkdWN0aW9uOgpgYGB7cn0KciRjYWxjdWxhdGVQY2FSZWR1Y3Rpb24oblBjcz01MCxuLm9kZ2VuZXM9M2UzKQpgYGAKVGhlIG5leHQgZmV3IHN0ZXBzIHdpbGwgbWFrZSBrTk4gZ3JhcGgsIGZpbmQgY2x1c3RlcnMgYW5kIGdlbmVyYXRlIGEgcXVpY2sgbGFyZ2VWaXMgZW1iZWRkaW5nIHRvIHZpc3VhbGl6ZSB0aGUgc3VicG9wdWxhdGlvbnM6CgpgYGB7cn0KciRtYWtlS25uR3JhcGgoaz00MCx0eXBlPSdQQ0EnLGNlbnRlcj1ULGRpc3RhbmNlPSdjb3NpbmUnKTsKciRnZXRLbm5DbHVzdGVycyhtZXRob2Q9aW5mb21hcC5jb21tdW5pdHksdHlwZT0nUENBJykKTSA8LSAzMDsgciRnZXRFbWJlZGRpbmcodHlwZT0nUENBJyxNPU0scGVycGxleGl0eT0zMCxnYW1tYT0xL00sYWxwaGE9MSkKYGBgCgpOb3cgd2UgY2FuIHZpc3VhbGl6ZSB0aGUgZW1iZWRkaW5nIHVzaW5nIHRoZSBkZXRlcm1pbmVkIGNsdXN0ZXJzOgpgYGB7cn0KciRwbG90RW1iZWRkaW5nKHR5cGU9J1BDQScsc2hvdy5sZWdlbmQ9RixtYXJrLmNsdXN0ZXJzPVQsbWluLmdyb3VwLnNpemU9NTAsc2h1ZmZsZS5jb2xvcnM9RixtYXJrLmNsdXN0ZXIuY2V4PTEsYWxwaGE9MC4xLG1haW49J2NsdXN0ZXJzIChsYXJnZVZpcyknKQpgYGAKCkFsdGVybmF0aXZlbHksIHdlIGNhbiBnZW5lcmF0ZSB0U05FIGVtYmVkZGluZyAodGFrZXMgbG9uZ2VyKQpgYGB7cn0KciRnZXRFbWJlZGRpbmcodHlwZT0nUENBJyxlbWJlZGRpbmdUeXBlPSd0U05FJyxwZXJwbGV4aXR5PTUwLHZlcmJvc2U9RikKCmBgYAoKYGBge3J9CnIkcGxvdEVtYmVkZGluZyh0eXBlPSdQQ0EnLGVtYmVkZGluZ1R5cGU9J3RTTkUnLHNob3cubGVnZW5kPUYsbWFyay5jbHVzdGVycz1ULG1pbi5ncm91cC5zaXplPTEsc2h1ZmZsZS5jb2xvcnM9RixtYXJrLmNsdXN0ZXIuY2V4PTEsYWxwaGE9MC4xLG1haW49J2NsdXN0ZXJzICh0U05FKScpCmBgYAoKV2UgY2FuIHVzZSB0aGUgc2FtZSBwbG90RW1iZWRkaW5nKCkgZnVuY3Rpb24gdG8gc2hvdyBhbGwga2luZHMgb2Ygb3RoZXIgdmFsdWVzLiBGb3IgaW5zdGFuY2UsIGxldCdzIGxvb2sgYXQgZGVwdGgsIG9yIGFuIGV4cHJlc3NvbiBwYXR0ZXJuIG9mIGEgZ2VuZToKYGBge3J9CnN0cihyJGRlcHRoKQpwYXIobWZyb3c9YygxLDIpKQpyJHBsb3RFbWJlZGRpbmcodHlwZT0nUENBJyxlbWJlZGRpbmdUeXBlPSd0U05FJyxjb2xvcnM9ciRkZXB0aCxzaHVmZmxlLmNvbG9ycz1GLG1hcmsuY2x1c3Rlci5jZXg9MSxhbHBoYT0wLjEsbWFpbj0nZGVwdGgnKQpnZW5lIDwtIkxZWiIKciRwbG90RW1iZWRkaW5nKHR5cGU9J1BDQScsZW1iZWRkaW5nVHlwZT0ndFNORScsY29sb3JzPXIkY291bnRzWyxnZW5lXSxzaHVmZmxlLmNvbG9ycz1GLG1hcmsuY2x1c3Rlci5jZXg9MSxhbHBoYT0wLjEsbWFpbj1nZW5lKQpgYGAKCgpXZSBjYW4gZ2VuZXJhdGUgbXVsdGlwbGUgcG90ZW50aWFsIGNsdXN0ZXJpbmdzLCB3aXRoIGRpZmZlcmVudCBuYW1lcy4gSGVyZSB3ZSdsbCB1c2UgbXVsdGlsZXZlbCBjbHVzdGVyaW5nOgpgYGB7cn0KciRnZXRLbm5DbHVzdGVycyhtZXRob2Q9bXVsdGlsZXZlbC5jb21tdW5pdHksdHlwZT0nUENBJyxuYW1lPSdtdWx0aWxldmVsJykKc3RyKHIkY2x1c3RlcnMpCgpgYGAKCkNvbXBhcmUgd2l0aCBpbmZvbWFwOgpgYGB7cn0KcGFyKG1mcm93PWMoMSwyKSkKciRwbG90RW1iZWRkaW5nKHR5cGU9J1BDQScsZW1iZWRkaW5nVHlwZT0ndFNORScsZ3JvdXBzPXIkY2x1c3RlcnMkUENBJGNvbW11bml0eSxzaG93LmxlZ2VuZD1GLG1hcmsuY2x1c3RlcnM9VCxtaW4uZ3JvdXAuc2l6ZT0xLHNodWZmbGUuY29sb3JzPUYsbWFyay5jbHVzdGVyLmNleD0xLGFscGhhPTAuMSxtYWluPSdpbmZvbWFwIGNsdXN0ZXJzICh0U05FKScpCnIkcGxvdEVtYmVkZGluZyh0eXBlPSdQQ0EnLGVtYmVkZGluZ1R5cGU9J3RTTkUnLGNsdXN0ZXJUeXBlPSdtdWx0aWxldmVsJyxzaG93LmxlZ2VuZD1GLG1hcmsuY2x1c3RlcnM9VCxtaW4uZ3JvdXAuc2l6ZT0xLHNodWZmbGUuY29sb3JzPUYsbWFyay5jbHVzdGVyLmNleD0xLGFscGhhPTAuMSxtYWluPSdtdWx0bGV2ZWwgY2x1c3RlcnMgKHRTTkUpJykKYGBgCgoKUnVuIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIG9uIHRoZSBpbmZvbWFwIGNsdXN0ZXJzOgpgYGB7cn0KciRnZXREaWZmZXJlbnRpYWxHZW5lcyh0eXBlPSdQQ0EnLHZlcmJvc2U9VCxjbHVzdGVyVHlwZT0nY29tbXVuaXR5JykKYGBgCgpWaXN1YWxpemUgdG9wIGdlbmVzOgpgYGB7cn0KbmFtZXMociRkaWZmZ2VuZXMpCmRlIDwtIHIkZGlmZmdlbmVzJFBDQVtbMV1dW1snNCddXTsKciRwbG90R2VuZUhlYXRtYXAoZ2VuZXM9cm93bmFtZXMoZGUpWzE6MTVdLGdyb3Vwcz1yJGNsdXN0ZXJzJFBDQVtbMV1dKQoKYGBgCgpTcG90LWNoZWNrIGEgZ2VuZQpgYGB7cn0KZ2VuZSA8LSJJR0hNIgpyJHBsb3RFbWJlZGRpbmcodHlwZT0nUENBJyxlbWJlZGRpbmdUeXBlPSd0U05FJyxjb2xvcnM9ciRjb3VudHNbLGdlbmVdLHNodWZmbGUuY29sb3JzPUYsbWFyay5jbHVzdGVyLmNleD0xLGFscGhhPTAuMSxtYWluPWdlbmUpCmBgYAoKCgpQYXRod2F5IG92ZXJkaXNwZXJzaW9uIGFuYWx5c2lzIChhIGxhIFBBR09EQTEpCgpGaXJzdCwgYnVpbGQgR08tPmdlbmUgZW52aXJvbm1lbnQ6CmBgYHtyIGV2YWw9Rn0Kc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KG9yZy5Icy5lZy5kYikpCiMgdHJhbnNsYXRlIGdlbmUgbmFtZXMgdG8gaWRzCmlkcyA8LSB1bmxpc3QobGFwcGx5KG1nZXQoY29sbmFtZXMociRjb3VudHMpLG9yZy5Icy5lZ0FMSUFTMkVHLGlmbm90Zm91bmQ9TkEpLGZ1bmN0aW9uKHgpIHhbMV0pKQojIHJldmVyc2UgbWFwCnJpZHMgPC0gbmFtZXMoaWRzKTsgbmFtZXMocmlkcykgPC0gaWRzOwojIGxpc3QgYWxsIHRoZSBpZHMgcGVyIEdPIGNhdGVnb3J5CmdvLmVudiA8LSBsaXN0MmVudihlYXBwbHkob3JnLkhzLmVnR08yQUxMRUdTLGZ1bmN0aW9uKHgpIGFzLmNoYXJhY3RlcihuYS5vbWl0KHJpZHNbeF0pKSkpCmBgYAoKTm93IHJ1biBvdmVyZGlzcGVyc2lvbiBhbmxheXNpcwpgYGB7ciBldmFsPUZ9CnIkdGVzdFBhdGh3YXlPdmVyZGlzcGVyc2lvbihnby5lbnYsdmVyYm9zZT1ULGNvcnJlbGF0aW9uLmRpc3RhbmNlLnRocmVzaG9sZD0wLjk1LHJlY2FsY3VsYXRlLnBjYT1GLHRvcC5hc3BlY3RzPTE1KQpgYGAKCgpXZSdsbCB1c2UgaGllcmFyY2hpY2FsIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIHJlc3VsdHMgaW5zdGVhZDoKYGBge3J9CnIkZ2V0SGllcmFyY2hpY2FsRGlmZkV4cHJlc3Npb25Bc3BlY3RzKHR5cGU9J1BDQScsY2x1c3Rlck5hbWU9J2NvbW11bml0eScsei50aHJlc2hvbGQ9MykKYGBgCgpXZSdsbCBtYWtlIGFuIGFwcCB3aXRoIHRoYXQsIG9yZGVyaW5nIHRoZSAiZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYXNwZWN0cyIgZXhwbGljaXRseSAob3RoZXJ3aXNlIGlmIHJvdyBjbHVzdGVyaW5nIGlzIG9taXR0ZWQgdGhleSdsbCBiZSBjbHVzdGVyZWQgYnkgc2ltaWxhcml0eSkKYGBge3J9CmFwcCA8LSBwMi5tYWtlLnBhZ29kYTEuYXBwKHIsaW5uZXIuY2x1c3RlcmluZz1UUlVFLGVtYmVkZGluZ1R5cGU9J3RTTkUnLGNsdXN0ZXJUeXBlPSdtdWx0aWxldmVsJyxtaW4uZ3JvdXAuc2l6ZT01MCxyb3cuY2x1c3RlcmluZz1saXN0KG9yZGVyPXJldigxOm5yb3cociRtaXNjJHBhdGh3YXlPRCR4dikpKSkKYGBgCgpTaG93IGFwcDoKYGBge3IgZXZhbD1GfQpzaG93LmFwcChhcHAsJ3BibWMnLGJyb3dzZT1UKQpgYGAKCgpBbHRlcm5hdGl2ZWx5LCB3ZSBjYW4gY3JlYXRlIGEgbW9yZSBhZHZhbmNlZCBwMiBhcHAsIHdoaWNoIGNhbiBiZSBzYXZlZCBhcyBhIHN0YW5kYWxvbmUgYmluYXJ5IGZpbGUgYW5kIHZpZXdlZCBmcm9tIHRoZSBicm93c2VyIGVpdGhlciBieSBhY2Nlc3NpbmcgdGhhdCBiaW5hcnkgZmlsZSBvbiB0aGUgbG9jYWwgZHJpdmUsIG9yIG92ZXIgcmVndWxhciBIVFRQIChpbiBvdGhlciB3b3Jkcywgd2l0aG91dCByZXF1aXJpbmcgYSBzZXJ2ZXIgb3IgYW4gUiBzZXNzaW9uKS4KCkZpcnN0LCBjb21waWxlIGdlbmUgc2V0cyBhbmQgbWV0YWRhdGEgdGhhdCB3ZSB3YW50IHRvIG1ha2UgYXZhaWxhYmxlIGluIHRoZSBvZmZsaW5lIHdlYiBhcHA6CmBgYHtyfQpsaWJyYXJ5KEdPLmRiKQp0ZXJtRGVzY3JpcHRpb25zIDwtIFRlcm0oR09URVJNW25hbWVzKGdvLmVudildKTsgIyBzYXZlcyBhIGdvb2QgbWludXRlIG9yIHNvIGNvbXBhcmVkIHRvIGluZGl2aWR1YWwgbG9va3VwcwpzbiA8LSBmdW5jdGlvbih4KSB7IG5hbWVzKHgpIDwtIHg7IHh9CmdlbmVTZXRzIDwtIGxhcHBseShzbihuYW1lcyhnby5lbnYpKSxmdW5jdGlvbih4KSB7CiAgbGlzdChwcm9wZXJ0aWVzPWxpc3QobG9ja2VkPVQsZ2VuZXNldG5hbWU9eCxzaG9ydGRlc2NyaXB0aW9uPWFzLmNoYXJhY3Rlcih0ZXJtRGVzY3JpcHRpb25zW3hdKSksZ2VuZXM9Yyhnby5lbnZbW3hdXSkpCn0pCgojIE1ha2UgbWV0YWRhdGEKYWRkaXRpb25hbE1ldGFkYXRhIDwtIGxpc3QobXVsdGlsZXZlbCA9IHAyLm1ldGFkYXRhLmZyb20uZmFjdG9yKHIkY2x1c3RlcnMkUENBJG11bHRpbGV2ZWwsIGRpc3BsYXluYW1lID0gJ011bHRpbGV2ZWwnLCBzID0gMC44KSkKYGBgCgpOb3cgZ2VuZXJhdGUgdGhlIHdlYiBhcHAgYW5kIHRoZSBiaW5hcnkgb3V0cHV0LgpgYGB7ciByZXN1bHRzPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIEdlbmVyYXRlIHRoZSB3ZWIgYXBwbGljYXRpb24KIyBkZW5kcm9ncmFtQ2VsbEdyb3VwcyBzcGVjaWZpZXMgdGhlIGNsdXN0ZXJpbmcgdGhhdCB3aWxsIGJlIHVzZWQgdG8gZ2VuZXJhdGUgdGhlIG1haW4gZGVuZHJvZ3JhbQpwMmFwcCA8LSBtYWtlLnAyLmFwcChyLCBkZW5kcm9ncmFtQ2VsbEdyb3VwcyA9IHIkY2x1c3RlcnMkUENBJGNvbW11bml0eSwgYWRkaXRpb25hbE1ldGFkYXRhID0gYWRkaXRpb25hbE1ldGFkYXRhLCBnZW5lU2V0cyA9IGdlbmVTZXRzKTsKCiMgT3B0aW9uYWwgc2hvd2luZyBvZiBhcHAKI3Nob3cuYXBwKHAydywgbmFtZT0nbmV3UGFnb2RhJykKCiMgU2F2ZSBzZXJpYWxpc2VkIHdlYiBvYmplY3QsIFJEUyBhcHAgYW5kIHNlc3Npb24gaW1hZ2UKcDJhcHAkc2VyaWFsaXplVG9TdGF0aWNGYXN0KGJpbmFyeS5maWxlbmFtZSA9ICdwYm1jOGsuYmluJyx2ZXJib3NlPVQpCmBgYAoKTm93IHRoZSBwYm1jOGsuYmluIGZpbGUgY2FuIGJlIHZpZXdlZCBsb2NhbGx5IHVzaW5nIGh0dHA6Ly9wa2xhYi5tZWQuaGFydmFyZC5lZHUvbmlrb2xhcy9wYWdvZGEyL2Zyb250ZW5kL2N1cnJlbnQvcGFnb2RhTG9jYWwvaW5kZXguaHRtbCAod2hpY2ggd2lsbCBhc2sgeW91IHRvIHNlbGVjdCBhIGZpbGUpLCBvciBieSBwbGFjaW5nIHRoZSAuYmluIGZpbGUgaW50byBzb21lIEhUVFAtYWNjZXNzaWJsZSBsb2NhdGlvbiBhbmQgdXNpbmcgYSBVUkwgbGlrZSB0aGlzLCB3aGVyZSAnZmlsZVVSTCcgYXJndW1lbnQgd291bGQgc3BlY2lmeSB0aGUgbG9jYXRpb24gYXQgd2hpY2ggdGhlIC5iaW4gZmlsZSBjYW4gYmUgYWNjZXNzZWQ6Cmh0dHA6Ly9wa2xhYi5tZWQuaGFydmFyZC5lZHUvbmlrb2xhcy9wYWdvZGEyL2Zyb250ZW5kL2N1cnJlbnQvcGFnb2RhVVJML2luZGV4Lmh0bWw/ZmlsZVVSTD1odHRwOi8vcGtsYWIubWVkLmhhcnZhcmQuZWR1L25pa29sYXMvcGFnb2RhMi9zdGF0aWNEZW1vL3BhZ29kYVB1YmxpY0RlbW8uYmluCgpUaGUgaW5kZXguaHRtbCBmaWxlIChhbmQgYXNzb2NpYXRlZCBzY3JpcHRzKSBhcmUgYXZhaWFsYmxlIGluIHRoZSAiaW5zdCIgZm9sZGVyIG9mIHRoZSBwYWdvZGEyIHBhY2thZ2UsIGluIGNhc2UgeW91IHdhbnQgdG8gc2V0IHVwIHlvdXIgb3duIGNvcHkuCgpIZXJlJ3MgYSB2aWV3IG9mIHRoZSBiaW5hcnkgZmlsZSB0aGF0IHdhcyBwcm9kdWNlZCBieSB0aGlzIHR1dG9yaWFsOgpodHRwOi8vcGtsYWIubWVkLmhhcnZhcmQuZWR1L3BldGVyay9wMi9yL2luZGV4Lmh0bWw/ZmlsZVVSTD1odHRwOi8vcGtsYWIubWVkLmhhcnZhcmQuZWR1L3BldGVyay9wMi9wYm1jOGsuYmlu