Molecule annotation

For inDrop experiments, the spliced/unspliced molecules can be annotated by:

  1. Using dropEst output pipeline to produce a 10x-like bam file: ~/dropEst/build/dropest -m -F -L eiEIBA -o run1 -g cellranger/refdata-cellranger-mm10-1.2.0/genes/genes.gtf -c ~/dropEst/configs/indrop_v3.xml *.bam

  2. Using velocyto.py to annotated spliced and unspliced reads, writing out a standard loom file: velocyto run -u Gene -o out -e SCG71 -m mm10_rmsk_srt.gtf -v SCG_71_tophat.filtered.sorted.bam UCSC/mm10/Annotation/Genes/genes.gtf

(note that it is also possible to annotated spliced/unspliced reads with dropEst directly, using -V option: ~/dropEst/dropest -V -C 6000 -m -g ucsc_mm10_exons.gtf.gz -c ~/dropEst/configs/indrop_v3.xml *.aligned.bam)

Please see the following shell script for a full set of commands used to prepare this particular example.

The example below starts with a loom file produced by velocyto.py, uses pagoda2 to obtain cell clusters/embedding, and then estimate/visualize velocity.

Data loading

Load the velocyto package:

library(velocyto.R)
Loading required package: Matrix

Load loom matrices: (to download pre-calculated loom matrices use wget http://pklab.med.harvard.edu/velocyto/mouseBM/SCG71.loom)

ldat <- read.loom.matrices(url("http://pklab.med.harvard.edu/velocyto/mouseBM/SCG71.loom"))
Error: is.character(name) is not TRUE

Normalize and cluster cells using pagoda2

Using spliced expression matrix as input to pagoda2.

emat <- ldat$spliced
hist(log10(colSums(emat)),col='wheat',xlab='cell size')

# this dataset has already been pre-filtered, but this is where one woudl do some filtering
emat <- emat[,colSums(emat)>=1e3]

Pagoda2 processing

Pagoda2 is used to generate cell embedding, cell clustering, as well as a more accurate cell-cell distance matrix. You can alternatively generate those using other tools, such as Seurat2, etc.

Create pagoda2 object, adjust variance:

library(pagoda2)
r <- Pagoda2$new(emat,modelType='plain',trim=10,log.scale=T)
2600 cells, 7301 genes; normalizing ... using plain model winsorizing ... log scale ... done.
r$adjustVariance(plot=T,do.par=T,gam.k=10)
calculating variance fit ... using gam 137 overdispersed genes ... 137 persisting ... done.

Run basic analysis steps to generate cell embedding and clustering, visualize:

r$calculatePcaReduction(nPcs=100,n.odgenes=3e3,maxit=300)
running PCA using 3000 OD genes ..
Loading required package: irlba
.. done
r$makeKnnGraph(k=30,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=multilevel.community,type='PCA',name='multilevel')
r$getEmbedding(type='PCA',embeddingType='tSNE',perplexity=50,verbose=T)
calculating distance ... pearson ...running tSNE using 16 cores:
Read the 2600 x 2600 data matrix successfully!
OpenMP is working...
Using no_dims = 2, perplexity = 50.000000, and theta = 0.500000
Computing input similarities...
Building tree...
Done in 9.36 seconds (sparsity = 0.084143)!
Learning embedding...
Iteration 50: error is 73.294295 (50 iterations in 3.70 seconds)
Iteration 100: error is 65.648297 (50 iterations in 3.19 seconds)
Iteration 150: error is 65.151406 (50 iterations in 3.17 seconds)
Iteration 200: error is 65.090414 (50 iterations in 3.24 seconds)
Iteration 250: error is 65.075571 (50 iterations in 3.27 seconds)
Iteration 300: error is 1.814009 (50 iterations in 3.11 seconds)
Iteration 350: error is 1.699950 (50 iterations in 3.08 seconds)
Iteration 400: error is 1.660607 (50 iterations in 3.01 seconds)
Iteration 450: error is 1.644676 (50 iterations in 2.98 seconds)
Iteration 500: error is 1.638744 (50 iterations in 2.96 seconds)
Iteration 550: error is 1.633982 (50 iterations in 3.00 seconds)
Iteration 600: error is 1.629732 (50 iterations in 2.99 seconds)
Iteration 650: error is 1.628101 (50 iterations in 3.12 seconds)
Iteration 700: error is 1.625991 (50 iterations in 3.27 seconds)
Iteration 750: error is 1.624012 (50 iterations in 3.15 seconds)
Iteration 800: error is 1.622847 (50 iterations in 3.27 seconds)
Iteration 850: error is 1.621847 (50 iterations in 3.31 seconds)
Iteration 900: error is 1.620831 (50 iterations in 3.34 seconds)
Iteration 950: error is 1.619936 (50 iterations in 3.21 seconds)
Iteration 1000: error is 1.618511 (50 iterations in 3.16 seconds)
Fitting performed in 63.52 seconds.

Plot embedding, labeling clusters (left) and “Xist” expression (which separates the male and female )

par(mfrow=c(1,2))
r$plotEmbedding(type='PCA',embeddingType='tSNE',show.legend=F,mark.clusters=T,min.group.size=10,shuffle.colors=F,mark.cluster.cex=1,alpha=0.3,main='cell clusters')
r$plotEmbedding(type='PCA',embeddingType='tSNE',colors=r$depth,main='depth')  
treating colors as a gradient with zlim: 1000.9 2939 

Velocity estimation

Prepare matrices and clustering data:

emat <- ldat$spliced; nmat <- ldat$unspliced; # disregarding spanning reads, as there are too few of them
emat <- emat[,rownames(r$counts)]; nmat <- nmat[,rownames(r$counts)]; # restrict to cells that passed p2 filter
# take cluster labels
cluster.label <- r$clusters$PCA$multilevel # take the cluster factor that was calculated by p2
cell.colors <- pagoda2:::fac2col(cluster.label)
# take embedding form p2
emb <- r$embeddings$PCA$tSNE

In addition to clustering and the t-SNE embedding, from the p2 processing we will also take a cell-cell distance, which will be better than the default whole-transcriptome correlation distance that velocyto.R would normally use.

cell.dist <- as.dist(1-armaCor(t(r$reductions$PCA)))

Filter genes based on the minimum average expresion magnitude (in at least one of the clusters), output total number of resulting valid genes:

emat <- filter.genes.by.cluster.expression(emat,cluster.label,min.max.cluster.average = 0.2)
nmat <- filter.genes.by.cluster.expression(nmat,cluster.label,min.max.cluster.average = 0.05)
length(intersect(rownames(emat),rownames(nmat)))
[1] 2541

Estimate RNA velocity (using gene-relative model with k=20 cell kNN pooling and using top/bottom 2% quantiles for gamma fit):

fit.quantile <- 0.02
rvel.cd <- gene.relative.velocity.estimates(emat,nmat,deltaT=1,kCells=25,cell.dist=cell.dist,fit.quantile=fit.quantile)
calculating cell knn ... done
calculating convolved matrices ... done
fitting gamma coefficients ... done. succesfful fit for 743 genes
filtered out 149 out of 743 genes due to low nmat-emat correlation
filtered out 27 out of 594 genes due to low nmat-emat slope
calculating RNA velocity shift ... done
calculating extrapolated cell state ... done

Visualize velocity on the t-SNE embedding, using velocity vector fields:

show.velocity.on.embedding.cor(emb,rvel.cd,n=200,scale='sqrt',cell.colors=ac(cell.colors,alpha=0.5),cex=0.8,arrow.scale=3,show.grid.flow=TRUE,min.grid.cell.mass=0.5,grid.n=40,arrow.lwd=1,do.par=F,cell.border.alpha = 0.1)
delta projections ... sqrt knn ... transition probs ... done
calculating arrows ... done
grid estimates ... grid.sd= 0.6580196  min.arrow.size= 0.01316039  max.grid.arrow.length= 0.05887327  done

Visualize a fit for a particular gene (we reuse rvel.cd to save on calcualtions here):

gene <- "Camp"
gene.relative.velocity.estimates(emat,nmat,deltaT=1,kCells = 25,kGenes=1,fit.quantile=fit.quantile,cell.emb=emb,cell.colors=cell.colors,cell.dist=cell.dist,show.gene=gene,old.fit=rvel.cd,do.par=T)
calculating convolved matrices ... done
[1] 1

Increase neighborhood size, which should give us a more idealistic view of the phase portraits, particularly when it comes to the main, macrophage differentiation streak:

gene <- "Camp"
gene.relative.velocity.estimates(emat,nmat,deltaT=1,kCells = 100,kGenes=1,fit.quantile=fit.quantile,cell.emb=emb,cell.colors=cell.colors,cell.dist=cell.dist,show.gene=gene,do.par=T)
calculating cell knn ... done
calculating convolved matrices ... done
[1] 1

LS0tCnRpdGxlOiAiTW91c2UgQk0gZXhhbXBsZSwgdXNpbmcgZHJvcEVzdCBhbmQgcGFnb2RhMiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMgTW9sZWN1bGUgYW5ub3RhdGlvbgpGb3IgaW5Ecm9wIGV4cGVyaW1lbnRzLCB0aGUgc3BsaWNlZC91bnNwbGljZWQgbW9sZWN1bGVzIGNhbiBiZSBhbm5vdGF0ZWQgYnk6CgoxLiBVc2luZyBbZHJvcEVzdCBvdXRwdXRdKGh0dHBzOi8vZ2l0aHViLmNvbS9obXMtZGJtaS9kcm9wRXN0KSBwaXBlbGluZSB0byBwcm9kdWNlIGEgMTB4LWxpa2UgYmFtIGZpbGU6CmB+L2Ryb3BFc3QvYnVpbGQvZHJvcGVzdCAtbSAtRiAtTCBlaUVJQkEgLW8gcnVuMSAtZyBjZWxscmFuZ2VyL3JlZmRhdGEtY2VsbHJhbmdlci1tbTEwLTEuMi4wL2dlbmVzL2dlbmVzLmd0ZiAtYyB+L2Ryb3BFc3QvY29uZmlncy9pbmRyb3BfdjMueG1sICouYmFtYAoKMi4gVXNpbmcgW3ZlbG9jeXRvLnB5XShodHRwOi8vdmVsb2N5dG8ub3JnL3ZlbG9jeXRvLnB5L3R1dG9yaWFsL2luZGV4Lmh0bWwjcnVubmluZy10aGUtY2xpKSB0byBhbm5vdGF0ZWQgc3BsaWNlZCBhbmQgdW5zcGxpY2VkIHJlYWRzLCB3cml0aW5nIG91dCBhIHN0YW5kYXJkIGxvb20gZmlsZToKYHZlbG9jeXRvIHJ1biAtdSBHZW5lIC1vIG91dCAtZSBTQ0c3MSAtbSBtbTEwX3Jtc2tfc3J0Lmd0ZiAtdiBTQ0dfNzFfdG9waGF0LmZpbHRlcmVkLnNvcnRlZC5iYW0gVUNTQy9tbTEwL0Fubm90YXRpb24vR2VuZXMvZ2VuZXMuZ3RmYAoKKG5vdGUgdGhhdCBpdCBpcyBhbHNvIHBvc3NpYmxlIHRvIGFubm90YXRlZCBzcGxpY2VkL3Vuc3BsaWNlZCByZWFkcyB3aXRoIGRyb3BFc3QgZGlyZWN0bHksIHVzaW5nIGAtVmAgb3B0aW9uOiBgfi9kcm9wRXN0L2Ryb3Blc3QgLVYgLUMgNjAwMCAtbSAtZyB1Y3NjX21tMTBfZXhvbnMuZ3RmLmd6IC1jIH4vZHJvcEVzdC9jb25maWdzL2luZHJvcF92My54bWwgKi5hbGlnbmVkLmJhbWApCgpQbGVhc2Ugc2VlIFt0aGUgZm9sbG93aW5nIHNoZWxsIHNjcmlwdF0oaHR0cDovL3BrbGFiLm1lZC5oYXJ2YXJkLmVkdS92ZWxvY3l0by9tb3VzZUJNL3ByZXByb2Nlc3Muc2gpIGZvciBhIGZ1bGwgc2V0IG9mIGNvbW1hbmRzIHVzZWQgdG8gcHJlcGFyZSB0aGlzIHBhcnRpY3VsYXIgZXhhbXBsZS4KCgpUaGUgZXhhbXBsZSBiZWxvdyBzdGFydHMgd2l0aCBhIGxvb20gZmlsZSBwcm9kdWNlZCBieSB2ZWxvY3l0by5weSwgdXNlcyBbcGFnb2RhMl0oaHR0cHM6Ly9naXRodWIuY29tL2htcy1kYm1pL3BhZ29kYTIpIHRvIG9idGFpbiBjZWxsIGNsdXN0ZXJzL2VtYmVkZGluZywgYW5kIHRoZW4gZXN0aW1hdGUvdmlzdWFsaXplIHZlbG9jaXR5LgoKCiMjIERhdGEgbG9hZGluZwoKTG9hZCB0aGUgdmVsb2N5dG8gcGFja2FnZToKYGBge3J9CmxpYnJhcnkodmVsb2N5dG8uUikKYGBgCgpMb2FkIGxvb20gbWF0cmljZXM6Cih0byBkb3dubG9hZCBwcmUtY2FsY3VsYXRlZCBsb29tIG1hdHJpY2VzIHVzZSBgd2dldCBodHRwOi8vcGtsYWIubWVkLmhhcnZhcmQuZWR1L3ZlbG9jeXRvL21vdXNlQk0vU0NHNzEubG9vbWApCmBgYHtyfQpsZGF0IDwtIHJlYWQubG9vbS5tYXRyaWNlcygiU0NHNzEubG9vbSIpCmBgYAoKCiMjIE5vcm1hbGl6ZSBhbmQgY2x1c3RlciBjZWxscyB1c2luZyBwYWdvZGEyClVzaW5nIHNwbGljZWQgZXhwcmVzc2lvbiBtYXRyaXggYXMgaW5wdXQgdG8gcGFnb2RhMi4KYGBge3J9CmVtYXQgPC0gbGRhdCRzcGxpY2VkCmhpc3QobG9nMTAoY29sU3VtcyhlbWF0KSksY29sPSd3aGVhdCcseGxhYj0nY2VsbCBzaXplJykKCiMgdGhpcyBkYXRhc2V0IGhhcyBhbHJlYWR5IGJlZW4gcHJlLWZpbHRlcmVkLCBidXQgdGhpcyBpcyB3aGVyZSBvbmUgd291ZGwgZG8gc29tZSBmaWx0ZXJpbmcKZW1hdCA8LSBlbWF0Wyxjb2xTdW1zKGVtYXQpPj0xZTNdCmBgYAoKIyMgUGFnb2RhMiBwcm9jZXNzaW5nCgpbUGFnb2RhMl0oaHR0cHM6Ly9naXRodWIuY29tL2htcy1kYm1pL3BhZ29kYTIpIGlzIHVzZWQgdG8gZ2VuZXJhdGUgY2VsbCBlbWJlZGRpbmcsIGNlbGwgY2x1c3RlcmluZywgYXMgd2VsbCBhcyBhIG1vcmUgYWNjdXJhdGUgY2VsbC1jZWxsIGRpc3RhbmNlIG1hdHJpeC4gWW91IGNhbiBhbHRlcm5hdGl2ZWx5IGdlbmVyYXRlIHRob3NlIHVzaW5nIG90aGVyIHRvb2xzLCBzdWNoIGFzIFNldXJhdDIsIGV0Yy4KCkNyZWF0ZSBwYWdvZGEyIG9iamVjdCwgYWRqdXN0IHZhcmlhbmNlOgpgYGB7ciBmaWcuaGVpZ2h0PTMsZmlnLndpZHRoPTZ9CmxpYnJhcnkocGFnb2RhMikKciA8LSBQYWdvZGEyJG5ldyhlbWF0LG1vZGVsVHlwZT0ncGxhaW4nLHRyaW09MTAsbG9nLnNjYWxlPVQpCnIkYWRqdXN0VmFyaWFuY2UocGxvdD1ULGRvLnBhcj1ULGdhbS5rPTEwKQpgYGAKClJ1biBiYXNpYyBhbmFseXNpcyBzdGVwcyB0byBnZW5lcmF0ZSBjZWxsIGVtYmVkZGluZyBhbmQgY2x1c3RlcmluZywgdmlzdWFsaXplOgpgYGB7cn0KciRjYWxjdWxhdGVQY2FSZWR1Y3Rpb24oblBjcz0xMDAsbi5vZGdlbmVzPTNlMyxtYXhpdD0zMDApCnIkbWFrZUtubkdyYXBoKGs9MzAsdHlwZT0nUENBJyxjZW50ZXI9VCxkaXN0YW5jZT0nY29zaW5lJyk7CnIkZ2V0S25uQ2x1c3RlcnMobWV0aG9kPW11bHRpbGV2ZWwuY29tbXVuaXR5LHR5cGU9J1BDQScsbmFtZT0nbXVsdGlsZXZlbCcpCnIkZ2V0RW1iZWRkaW5nKHR5cGU9J1BDQScsZW1iZWRkaW5nVHlwZT0ndFNORScscGVycGxleGl0eT01MCx2ZXJib3NlPVQpCmBgYAoKUGxvdCBlbWJlZGRpbmcsIGxhYmVsaW5nIGNsdXN0ZXJzIChsZWZ0KSBhbmQgIlhpc3QiIGV4cHJlc3Npb24gKHdoaWNoIHNlcGFyYXRlcyB0aGUgbWFsZSBhbmQgZmVtYWxlICkKYGBge3IgZmlnLmhlaWdodD00LGZpZy53aWR0aD04fQpwYXIobWZyb3c9YygxLDIpKQpyJHBsb3RFbWJlZGRpbmcodHlwZT0nUENBJyxlbWJlZGRpbmdUeXBlPSd0U05FJyxzaG93LmxlZ2VuZD1GLG1hcmsuY2x1c3RlcnM9VCxtaW4uZ3JvdXAuc2l6ZT0xMCxzaHVmZmxlLmNvbG9ycz1GLG1hcmsuY2x1c3Rlci5jZXg9MSxhbHBoYT0wLjMsbWFpbj0nY2VsbCBjbHVzdGVycycpCnIkcGxvdEVtYmVkZGluZyh0eXBlPSdQQ0EnLGVtYmVkZGluZ1R5cGU9J3RTTkUnLGNvbG9ycz1yJGRlcHRoLG1haW49J2RlcHRoJykgIAoKYGBgCgojIyBWZWxvY2l0eSBlc3RpbWF0aW9uCgpQcmVwYXJlIG1hdHJpY2VzIGFuZCBjbHVzdGVyaW5nIGRhdGE6CmBgYHtyfQplbWF0IDwtIGxkYXQkc3BsaWNlZDsgbm1hdCA8LSBsZGF0JHVuc3BsaWNlZDsgIyBkaXNyZWdhcmRpbmcgc3Bhbm5pbmcgcmVhZHMsIGFzIHRoZXJlIGFyZSB0b28gZmV3IG9mIHRoZW0KZW1hdCA8LSBlbWF0Wyxyb3duYW1lcyhyJGNvdW50cyldOyBubWF0IDwtIG5tYXRbLHJvd25hbWVzKHIkY291bnRzKV07ICMgcmVzdHJpY3QgdG8gY2VsbHMgdGhhdCBwYXNzZWQgcDIgZmlsdGVyCgojIHRha2UgY2x1c3RlciBsYWJlbHMKY2x1c3Rlci5sYWJlbCA8LSByJGNsdXN0ZXJzJFBDQSRtdWx0aWxldmVsICMgdGFrZSB0aGUgY2x1c3RlciBmYWN0b3IgdGhhdCB3YXMgY2FsY3VsYXRlZCBieSBwMgpjZWxsLmNvbG9ycyA8LSBwYWdvZGEyOjo6ZmFjMmNvbChjbHVzdGVyLmxhYmVsKQojIHRha2UgZW1iZWRkaW5nIGZvcm0gcDIKZW1iIDwtIHIkZW1iZWRkaW5ncyRQQ0EkdFNORQpgYGAKCgpJbiBhZGRpdGlvbiB0byBjbHVzdGVyaW5nIGFuZCB0aGUgdC1TTkUgZW1iZWRkaW5nLCBmcm9tIHRoZSBwMiBwcm9jZXNzaW5nIHdlIHdpbGwgYWxzbyB0YWtlIGEgY2VsbC1jZWxsIGRpc3RhbmNlLCB3aGljaCB3aWxsIGJlIGJldHRlciB0aGFuIHRoZSBkZWZhdWx0IHdob2xlLXRyYW5zY3JpcHRvbWUgY29ycmVsYXRpb24gZGlzdGFuY2UgdGhhdCB2ZWxvY3l0by5SIHdvdWxkIG5vcm1hbGx5IHVzZS4KYGBge3J9CmNlbGwuZGlzdCA8LSBhcy5kaXN0KDEtYXJtYUNvcih0KHIkcmVkdWN0aW9ucyRQQ0EpKSkKYGBgCgpGaWx0ZXIgZ2VuZXMgYmFzZWQgb24gdGhlIG1pbmltdW0gYXZlcmFnZSBleHByZXNpb24gbWFnbml0dWRlIChpbiBhdCBsZWFzdCBvbmUgb2YgdGhlIGNsdXN0ZXJzKSwgb3V0cHV0IHRvdGFsIG51bWJlciBvZiByZXN1bHRpbmcgdmFsaWQgZ2VuZXM6CmBgYHtyfQplbWF0IDwtIGZpbHRlci5nZW5lcy5ieS5jbHVzdGVyLmV4cHJlc3Npb24oZW1hdCxjbHVzdGVyLmxhYmVsLG1pbi5tYXguY2x1c3Rlci5hdmVyYWdlID0gMC4yKQpubWF0IDwtIGZpbHRlci5nZW5lcy5ieS5jbHVzdGVyLmV4cHJlc3Npb24obm1hdCxjbHVzdGVyLmxhYmVsLG1pbi5tYXguY2x1c3Rlci5hdmVyYWdlID0gMC4wNSkKbGVuZ3RoKGludGVyc2VjdChyb3duYW1lcyhlbWF0KSxyb3duYW1lcyhlbWF0KSkpCmBgYAoKRXN0aW1hdGUgUk5BIHZlbG9jaXR5ICh1c2luZyBnZW5lLXJlbGF0aXZlIG1vZGVsIHdpdGggaz0yMCBjZWxsIGtOTiBwb29saW5nIGFuZCB1c2luZyB0b3AvYm90dG9tIDIlIHF1YW50aWxlcyBmb3IgZ2FtbWEgZml0KToKYGBge3J9CmZpdC5xdWFudGlsZSA8LSAwLjAyCnJ2ZWwuY2QgPC0gZ2VuZS5yZWxhdGl2ZS52ZWxvY2l0eS5lc3RpbWF0ZXMoZW1hdCxubWF0LGRlbHRhVD0xLGtDZWxscz0yNSxjZWxsLmRpc3Q9Y2VsbC5kaXN0LGZpdC5xdWFudGlsZT1maXQucXVhbnRpbGUpCmBgYAoKClZpc3VhbGl6ZSB2ZWxvY2l0eSBvbiB0aGUgdC1TTkUgZW1iZWRkaW5nLCB1c2luZyB2ZWxvY2l0eSB2ZWN0b3IgZmllbGRzOgpgYGB7ciBmaWcuaGVpZ2h0PTgsZmlnLndpZHRoPTh9CnNob3cudmVsb2NpdHkub24uZW1iZWRkaW5nLmNvcihlbWIscnZlbC5jZCxuPTIwMCxzY2FsZT0nc3FydCcsY2VsbC5jb2xvcnM9YWMoY2VsbC5jb2xvcnMsYWxwaGE9MC41KSxjZXg9MC44LGFycm93LnNjYWxlPTMsc2hvdy5ncmlkLmZsb3c9VFJVRSxtaW4uZ3JpZC5jZWxsLm1hc3M9MC41LGdyaWQubj00MCxhcnJvdy5sd2Q9MSxkby5wYXI9RixjZWxsLmJvcmRlci5hbHBoYSA9IDAuMSkKYGBgCgoKVmlzdWFsaXplIGEgZml0IGZvciBhIHBhcnRpY3VsYXIgZ2VuZSAod2UgcmV1c2UgcnZlbC5jZCB0byBzYXZlIG9uIGNhbGN1YWx0aW9ucyBoZXJlKToKYGBge3IgZmlnLmhlaWdodD0yLjUsZmlnLndpZHRoPTl9CmdlbmUgPC0gIkNhbXAiCmdlbmUucmVsYXRpdmUudmVsb2NpdHkuZXN0aW1hdGVzKGVtYXQsbm1hdCxkZWx0YVQ9MSxrQ2VsbHMgPSAyNSxrR2VuZXM9MSxmaXQucXVhbnRpbGU9Zml0LnF1YW50aWxlLGNlbGwuZW1iPWVtYixjZWxsLmNvbG9ycz1jZWxsLmNvbG9ycyxjZWxsLmRpc3Q9Y2VsbC5kaXN0LHNob3cuZ2VuZT1nZW5lLG9sZC5maXQ9cnZlbC5jZCxkby5wYXI9VCkKYGBgCkluY3JlYXNlIG5laWdoYm9yaG9vZCBzaXplLCB3aGljaCBzaG91bGQgZ2l2ZSB1cyBhIG1vcmUgaWRlYWxpc3RpYyB2aWV3IG9mIHRoZSBwaGFzZSBwb3J0cmFpdHMsIHBhcnRpY3VsYXJseSB3aGVuIGl0IGNvbWVzIHRvIHRoZSBtYWluLCBtYWNyb3BoYWdlIGRpZmZlcmVudGlhdGlvbiBzdHJlYWs6CmBgYHtyIGZpZy5oZWlnaHQ9Mi41LGZpZy53aWR0aD05fQpnZW5lIDwtICJDYW1wIgpnZW5lLnJlbGF0aXZlLnZlbG9jaXR5LmVzdGltYXRlcyhlbWF0LG5tYXQsZGVsdGFUPTEsa0NlbGxzID0gMTAwLGtHZW5lcz0xLGZpdC5xdWFudGlsZT1maXQucXVhbnRpbGUsY2VsbC5lbWI9ZW1iLGNlbGwuY29sb3JzPWNlbGwuY29sb3JzLGNlbGwuZGlzdD1jZWxsLmRpc3Qsc2hvdy5nZW5lPWdlbmUsZG8ucGFyPVQpCmBgYAo=