Download data here. For how to construct the data objects, see Tutorial.

Quick facts about the datasets:


Load library and data.

library(FRmatch)
load("sce-human-M1-10X-subclass.rda")
load("sce-human-M1C-SS4-subclass.rda")

Rename the data objects for simplification.

## query data
sce.query <- sce.human.M1C.SS4.subclass
## reference data
sce.ref <- sce.human.M1.10X.subclass

Take a look at the gene expression data distribution. We preprocessed the 10X data by taking the log1p() transformation of the raw count data; we preprocessed the SMART-seq data by taking the logCPM of the raw count data. The log-transformed data were stored in the @assays$logcounts slot of each data object.

par(mfrow=c(1,2))
hist(logcounts(sce.query))
stats.query <- summary(as.vector(logcounts(sce.query)))
pzero.query <- sum(as.vector(logcounts(sce.query))==0)/length(as.vector(logcounts(sce.query)))
legend("topright", paste(c(names(stats.query), "zero pct."),"=",round(c(stats.query,pzero.query),3)))
hist(logcounts(sce.ref))
stats.ref <- summary(as.vector(logcounts(sce.ref)))
pzero.ref <- sum(as.vector(logcounts(sce.ref))==0)/length(as.vector(logcounts(sce.ref)))
legend("topright", paste(c(names(stats.ref), "zero pct."),"=",round(c(stats.ref,pzero.ref),3)))

The SMART-seq and 10X data form quite different data distributions. It is noticeable that the SMART-seq protocol has better sensitivity, and the 10X data has more zero values (potentially higher dropout rate). For the cross-platform matching, normalization is necessary!

sce.query.norm <- normalization(sce.query, scale=TRUE, norm.by="mean") #SMART-seq
sce.ref.scale <- normalization(sce.ref, scale=TRUE) #10X data only need scaling

Check again the normalized data distribution. Now they range from 0 to 1, and form similar distribution.

par(mfrow=c(1,2))
hist(logcounts(sce.query.norm))
hist(logcounts(sce.ref.scale))

Take an overview of the cell type clusters in both datasets. Only cells with cluster labels are used, i.e. removed cells without label from the original publication. In FR-Match, we are going to ignore the clusters with very few cells.

## cluster size
plot_clusterSize(sce.query, sce.ref, name.E1 = "Huamn M1C SS4", name.E2 = "Huamn M1 10X")

The following codes are suggested to plot all cell type barcode plots in an assigned folder. NOT RUN HERE.

ref.clusters <- unique(colData(sce.ref)$cluster_membership)
for(cl in ref.clusters){
  plot_cluster_by_markers(sce.ref.scale, cluster.name = cl, name.E1 = "M1_10X_",
                          filename = paste0("plot_barcodes_M1_10X/scale_",cl,".pdf"))
}

query.clusters <- unique(colData(sce.query)$cluster_membership)
for(cl in query.clusters){
  plot_cluster_by_markers(sce.query.norm, sce.query.norm, cluster.name = cl, name.E2 = "M1C_SS4_",
                          filename = paste0("plot_barcodes_M1C_SS4/norm_",cl,".pdf"))
}

Run FR-Match

Cell-to-cluster matching

Below we provide some guidance on parameter configuration.

  • We ignore small clusters that have less than 5 cells (filter.size=5)
  • For cell2cluster matching, it is recommended to use subsamp.size=10. Your best choice depends on your data. The choice of this parameter will impact the matching score (i.e. p-value), which is used to determine a match or unassigned match.
  • use.cosine=TRUE indicates to use the cosine distance instead of the Euclidean distance in the calculation, which will bring more robustness in matching between different types of samples.
  • prefix=c("ss4.","tenx.") is the customizable names of your query and reference data.
rst.cell2cluster <- FRmatch_cell2cluster(sce.query.norm, sce.ref.scale, 
                                         filter.size=5, subsamp.size=10, use.cosine=TRUE,
                                         prefix=c("ss4.","tenx."))

The easiest way to look at the results is to use its plot function, which shows the proportion of query cells that are matched to the reference cluster. Note that the column sum is 1.

plot_FRmatch_cell2cluster(rst.cell2cluster, type="match.prop")

Matching results of each query cell can be accessed below.

rst.cell2cluster$cell2cluster

Cluster-to-cluster matching

Though it is suggested to use slightly larger subsampling size (subsamp.size=20 by default) for the cluster-to-cluster matching. Here, we use subsamp.size=10 again in this example. Your best setting may depend on your data, therefore we recommend to check the cluster sizes in the beginning.

rst <- FRmatch(sce.query.norm, sce.ref.scale, 
               filter.size=5, subsamp.size=10, use.cosine=TRUE,
               prefix=c("ss4.","tenx."))

Visualize the results.

plot_FRmatch(rst)

plot_FRmatch(rst, type="padj")

The prediction function below returns the most similar reference cluster for each query cluster, though some adjusted p-values may be below the threshold (indicating not a firm match).

predict_most_similar_cluster(rst)
LS0tCnRpdGxlOiAiTWF0Y2hpbmcgaHVtYW4gTTEgY2VsbCBzdWJjbGFzc2VzIGFjcm9zcyBkaWZmZXJlbnQgcGxhdGZvcm1zIC0gU01BUlQtc2VxIGFuZCAxMFgiCm91dHB1dDogaHRtbF9ub3RlYm9vawphdXRob3I6ICJZdW4gKFJlbmVlKSBaaGFuZywgemhhbmd5QGpjdmkub3JnIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclQiAlZCwgJVknKWAiCi0tLQoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGNhY2hlID0gVFJVRSkKc2V0d2QoIn4vRG9jdW1lbnRzL0JJQ0NOLUJyYWluU3RhbmRhcmRzLTIwMjAvQW5sYXlzZXMvTWFwcGluZy9IdW1hbi1NMS9GUm1hdGNoL3R1dG9yaWFsLyIpCmBgYAoKLS0tCgpEb3dubG9hZCBkYXRhIFtoZXJlXShodHRwczovL2pjdmlvZmZpY2UzNjUtbXkuc2hhcmVwb2ludC5jb20vOmY6L3IvcGVyc29uYWwvemhhbmd5X2pjdmlfb3JnL0RvY3VtZW50cy9GUi1NYXRjaCUyMGRlbW8lMjBkYXRhL1R1dG9yaWFsX2h1bWFuX00xXzEwWF9TUzQ/Y3NmPTEmd2ViPTEmZT0yTkVWNXMpLiAKRm9yIGhvdyB0byBjb25zdHJ1Y3QgdGhlIGRhdGEgb2JqZWN0cywgc2VlIFtUdXRvcmlhbF0oaHR0cHM6Ly9qY3ZlbnRlcmluc3RpdHV0ZS5naXRodWIuaW8vY2VsbGlncmF0ZS9GUm1hdGNoLXZpZ25ldHRlLmh0bWwpLgoKUXVpY2sgZmFjdHMgYWJvdXQgdGhlIGRhdGFzZXRzOgoKKyBSZWZlcmVuY2UgZGF0YQogICsgQW5hdG9taWMgcmVnaW9uOiBoZWFsdGh5IGh1bWFuIHByaW1hcnkgbW90b3IgY29ydGV4IChNMSkKICArIEV4cGVyaW1lbnRhbCBwbGF0Zm9ybTogMTBYIENocm9taXVtIHYzIChDdjMpCiAgKyBTYW1wbGUgdHlwZTogc2luZ2xlLW51Y2xldXMKICArIENpdGF0aW9uOiBbRXZvbHV0aW9uIG9mIGNlbGx1bGFyIGRpdmVyc2l0eSBpbiBwcmltYXJ5IG1vdG9yIGNvcnRleCBvZiBodW1hbiwgbWFybW9zZXQgbW9ua2V5LCBhbmQgbW91c2VdKGh0dHBzOi8vd3d3LmJpb3J4aXYub3JnL2NvbnRlbnQvMTAuMTEwMS8yMDIwLjAzLjMxLjAxNjk3MnYyKS4gRGF0YSB1c2VkIGhlcmUgaXMgdGhlIGh1bWFuIEN2MyBzdWJzZXQgb2YgdGhlIGNyb3NzLXNwZWNpZXMgTTEgZGF0YSBwdWJsaXNoZWQgaW4gdGhlIGFib3ZlIHB1YmxpY2F0aW9uIChzZWUgRmlndXJlIDFiKS4KICArIERhdGEgcG9ydGFsOiBbSHVtYW4gTTEgMTB4XShodHRwczovL3BvcnRhbC5icmFpbi1tYXAub3JnL2F0bGFzZXMtYW5kLWRhdGEvcm5hc2VxL2h1bWFuLW0xLTEweCkuIEFsc28gdXNlZCBhcyB0aGUgcmVmZXJlbmNlIGRhdGFzZXQgaW4gdGhlIFtBemltdXRoIGFwcF0oaHR0cHM6Ly9hcHAuYXppbXV0aC5odWJtYXBjb25zb3J0aXVtLm9yZy9hcHAvaHVtYW4tbW90b3Jjb3J0ZXgpLgogIAorIFF1ZXJ5IGRhdGEKICArIEFuYXRvbWljIHJlZ2lvbjogaGVhbHRoeSBodW1hbiBwcmltYXJ5IG1vdG9yIGNvcnRleCAoTTFDIG9yIE0xKQogICsgRXhwZXJpbWVudGFsIHBsYXRmb3JtOiBTTUFSVC1TZXEgdjQgKFNTNCkKICArIFNhbXBsZSB0eXBlOiBzaW5nbGUtbnVjbGV1cwogICsgRGF0YSBwb3J0YWw6IFtIdW1hbiBNdWx0aXBsZSBDb3J0aWNhbCBBcmVhcyBTTUFSVC1zZXFdKGh0dHBzOi8vcG9ydGFsLmJyYWluLW1hcC5vcmcvYXRsYXNlcy1hbmQtZGF0YS9ybmFzZXEvaHVtYW4tbXVsdGlwbGUtY29ydGljYWwtYXJlYXMtc21hcnQtc2VxKS4gQWxzbyB1c2VkIGFzIHRoZSBkZW1vIGRhdGFzZXQgaW4gdGhlIFtBemltdXRoIGFwcF0oaHR0cHM6Ly9hcHAuYXppbXV0aC5odWJtYXBjb25zb3J0aXVtLm9yZy9hcHAvaHVtYW4tbW90b3Jjb3J0ZXgpLgoKLS0tCgpMb2FkIGxpYnJhcnkgYW5kIGRhdGEuCmBgYHtyfQpsaWJyYXJ5KEZSbWF0Y2gpCmxvYWQoInNjZS1odW1hbi1NMS0xMFgtc3ViY2xhc3MucmRhIikKbG9hZCgic2NlLWh1bWFuLU0xQy1TUzQtc3ViY2xhc3MucmRhIikKYGBgCgoKUmVuYW1lIHRoZSBkYXRhIG9iamVjdHMgZm9yIHNpbXBsaWZpY2F0aW9uLgpgYGB7cn0KIyMgcXVlcnkgZGF0YQpzY2UucXVlcnkgPC0gc2NlLmh1bWFuLk0xQy5TUzQuc3ViY2xhc3MKIyMgcmVmZXJlbmNlIGRhdGEKc2NlLnJlZiA8LSBzY2UuaHVtYW4uTTEuMTBYLnN1YmNsYXNzCmBgYAoKVGFrZSBhIGxvb2sgYXQgdGhlIGdlbmUgZXhwcmVzc2lvbiBkYXRhIGRpc3RyaWJ1dGlvbi4gX1dlIHByZXByb2Nlc3NlZCB0aGUgMTBYIGRhdGEgYnkgdGFraW5nIHRoZSBgbG9nMXAoKWAgdHJhbnNmb3JtYXRpb24gb2YgdGhlIHJhdyBjb3VudCBkYXRhOyB3ZSBwcmVwcm9jZXNzZWQgdGhlIFNNQVJULXNlcSBkYXRhIGJ5IHRha2luZyB0aGUgbG9nQ1BNIG9mIHRoZSByYXcgY291bnQgZGF0YS4gVGhlIGxvZy10cmFuc2Zvcm1lZCBkYXRhIHdlcmUgc3RvcmVkIGluIHRoZSBgQGFzc2F5cyRsb2djb3VudHNgIHNsb3Qgb2YgZWFjaCBkYXRhIG9iamVjdC5fIApgYGB7ciwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9Mi41fQpwYXIobWZyb3c9YygxLDIpKQpoaXN0KGxvZ2NvdW50cyhzY2UucXVlcnkpKQpzdGF0cy5xdWVyeSA8LSBzdW1tYXJ5KGFzLnZlY3Rvcihsb2djb3VudHMoc2NlLnF1ZXJ5KSkpCnB6ZXJvLnF1ZXJ5IDwtIHN1bShhcy52ZWN0b3IobG9nY291bnRzKHNjZS5xdWVyeSkpPT0wKS9sZW5ndGgoYXMudmVjdG9yKGxvZ2NvdW50cyhzY2UucXVlcnkpKSkKbGVnZW5kKCJ0b3ByaWdodCIsIHBhc3RlKGMobmFtZXMoc3RhdHMucXVlcnkpLCAiemVybyBwY3QuIiksIj0iLHJvdW5kKGMoc3RhdHMucXVlcnkscHplcm8ucXVlcnkpLDMpKSkKaGlzdChsb2djb3VudHMoc2NlLnJlZikpCnN0YXRzLnJlZiA8LSBzdW1tYXJ5KGFzLnZlY3Rvcihsb2djb3VudHMoc2NlLnJlZikpKQpwemVyby5yZWYgPC0gc3VtKGFzLnZlY3Rvcihsb2djb3VudHMoc2NlLnJlZikpPT0wKS9sZW5ndGgoYXMudmVjdG9yKGxvZ2NvdW50cyhzY2UucmVmKSkpCmxlZ2VuZCgidG9wcmlnaHQiLCBwYXN0ZShjKG5hbWVzKHN0YXRzLnJlZiksICJ6ZXJvIHBjdC4iKSwiPSIscm91bmQoYyhzdGF0cy5yZWYscHplcm8ucmVmKSwzKSkpCmBgYAoKVGhlIFNNQVJULXNlcSBhbmQgMTBYIGRhdGEgZm9ybSBxdWl0ZSBkaWZmZXJlbnQgZGF0YSBkaXN0cmlidXRpb25zLiBJdCBpcyBub3RpY2VhYmxlIHRoYXQgdGhlIFNNQVJULXNlcSBwcm90b2NvbCBoYXMgYmV0dGVyIHNlbnNpdGl2aXR5LCBhbmQgdGhlIDEwWCBkYXRhIGhhcyBtb3JlIHplcm8gdmFsdWVzIChwb3RlbnRpYWxseSBoaWdoZXIgZHJvcG91dCByYXRlKS4KX19Gb3IgdGhlIGNyb3NzLXBsYXRmb3JtIG1hdGNoaW5nLCBub3JtYWxpemF0aW9uIGlzIG5lY2Vzc2FyeSFfXwpgYGB7cn0Kc2NlLnF1ZXJ5Lm5vcm0gPC0gbm9ybWFsaXphdGlvbihzY2UucXVlcnksIHNjYWxlPVRSVUUsIG5vcm0uYnk9Im1lYW4iKSAjU01BUlQtc2VxCnNjZS5yZWYuc2NhbGUgPC0gbm9ybWFsaXphdGlvbihzY2UucmVmLCBzY2FsZT1UUlVFKSAjMTBYIGRhdGEgb25seSBuZWVkIHNjYWxpbmcKYGBgCgpDaGVjayBhZ2FpbiB0aGUgbm9ybWFsaXplZCBkYXRhIGRpc3RyaWJ1dGlvbi4gTm93IHRoZXkgcmFuZ2UgZnJvbSAwIHRvIDEsIGFuZCBmb3JtIHNpbWlsYXIgZGlzdHJpYnV0aW9uLgpgYGB7ciwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9Mi41fQpwYXIobWZyb3c9YygxLDIpKQpoaXN0KGxvZ2NvdW50cyhzY2UucXVlcnkubm9ybSkpCmhpc3QobG9nY291bnRzKHNjZS5yZWYuc2NhbGUpKQpgYGAKCgpUYWtlIGFuIG92ZXJ2aWV3IG9mIHRoZSBjZWxsIHR5cGUgY2x1c3RlcnMgaW4gYm90aCBkYXRhc2V0cy4gX09ubHkgY2VsbHMgd2l0aCBjbHVzdGVyIGxhYmVscyBhcmUgdXNlZCwgaS5lLiByZW1vdmVkIGNlbGxzIHdpdGhvdXQgbGFiZWwgZnJvbSB0aGUgb3JpZ2luYWwgcHVibGljYXRpb24uIEluIEZSLU1hdGNoLCB3ZSBhcmUgZ29pbmcgdG8gaWdub3JlIHRoZSBjbHVzdGVycyB3aXRoIHZlcnkgZmV3IGNlbGxzLl8KYGBge3IsIGZpZy53aWR0aD01LGZpZy5oZWlnaHQ9NH0KIyMgY2x1c3RlciBzaXplCnBsb3RfY2x1c3RlclNpemUoc2NlLnF1ZXJ5LCBzY2UucmVmLCBuYW1lLkUxID0gIkh1YW1uIE0xQyBTUzQiLCBuYW1lLkUyID0gIkh1YW1uIE0xIDEwWCIpCmBgYAoKVGhlIGZvbGxvd2luZyBjb2RlcyBhcmUgc3VnZ2VzdGVkIHRvIHBsb3QgYWxsIGNlbGwgdHlwZSBiYXJjb2RlIHBsb3RzIGluIGFuIGFzc2lnbmVkIGZvbGRlci4gTk9UIFJVTiBIRVJFLgoKYGBge3IsIGV2YWw9RkFMU0V9CnJlZi5jbHVzdGVycyA8LSB1bmlxdWUoY29sRGF0YShzY2UucmVmKSRjbHVzdGVyX21lbWJlcnNoaXApCmZvcihjbCBpbiByZWYuY2x1c3RlcnMpewogIHBsb3RfY2x1c3Rlcl9ieV9tYXJrZXJzKHNjZS5yZWYuc2NhbGUsIGNsdXN0ZXIubmFtZSA9IGNsLCBuYW1lLkUxID0gIk0xXzEwWF8iLAogICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVuYW1lID0gcGFzdGUwKCJwbG90X2JhcmNvZGVzX00xXzEwWC9zY2FsZV8iLGNsLCIucGRmIikpCn0KCnF1ZXJ5LmNsdXN0ZXJzIDwtIHVuaXF1ZShjb2xEYXRhKHNjZS5xdWVyeSkkY2x1c3Rlcl9tZW1iZXJzaGlwKQpmb3IoY2wgaW4gcXVlcnkuY2x1c3RlcnMpewogIHBsb3RfY2x1c3Rlcl9ieV9tYXJrZXJzKHNjZS5xdWVyeS5ub3JtLCBzY2UucXVlcnkubm9ybSwgY2x1c3Rlci5uYW1lID0gY2wsIG5hbWUuRTIgPSAiTTFDX1NTNF8iLAogICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVuYW1lID0gcGFzdGUwKCJwbG90X2JhcmNvZGVzX00xQ19TUzQvbm9ybV8iLGNsLCIucGRmIikpCn0KYGBgCgoKIyBSdW4gRlItTWF0Y2gKCiMjIENlbGwtdG8tY2x1c3RlciBtYXRjaGluZwoKQmVsb3cgd2UgcHJvdmlkZSBzb21lIGd1aWRhbmNlIG9uIHBhcmFtZXRlciBjb25maWd1cmF0aW9uLgoKICArIFdlIGlnbm9yZSBzbWFsbCBjbHVzdGVycyB0aGF0IGhhdmUgbGVzcyB0aGFuIDUgY2VsbHMgKGBmaWx0ZXIuc2l6ZT01YCkKICArIEZvciBjZWxsMmNsdXN0ZXIgbWF0Y2hpbmcsIGl0IGlzIHJlY29tbWVuZGVkIHRvIHVzZSBgc3Vic2FtcC5zaXplPTEwYC4gWW91ciBiZXN0IGNob2ljZSBkZXBlbmRzIG9uIHlvdXIgZGF0YS4gVGhlIGNob2ljZSBvZiB0aGlzIHBhcmFtZXRlciB3aWxsIGltcGFjdCB0aGUgbWF0Y2hpbmcgc2NvcmUgKGkuZS4gcC12YWx1ZSksIHdoaWNoIGlzIHVzZWQgdG8gZGV0ZXJtaW5lIGEgbWF0Y2ggb3IgdW5hc3NpZ25lZCBtYXRjaC4KICArIGB1c2UuY29zaW5lPVRSVUVgIGluZGljYXRlcyB0byB1c2UgdGhlIGNvc2luZSBkaXN0YW5jZSBpbnN0ZWFkIG9mIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2UgaW4gdGhlIGNhbGN1bGF0aW9uLCB3aGljaCB3aWxsIGJyaW5nIG1vcmUgcm9idXN0bmVzcyBpbiBtYXRjaGluZyBiZXR3ZWVuIGRpZmZlcmVudCB0eXBlcyBvZiBzYW1wbGVzLgogICsgYHByZWZpeD1jKCJzczQuIiwidGVueC4iKWAgaXMgdGhlIGN1c3RvbWl6YWJsZSBuYW1lcyBvZiB5b3VyIHF1ZXJ5IGFuZCByZWZlcmVuY2UgZGF0YS4KCmBgYHtyLCBldmFsPUZBTFNFfQpyc3QuY2VsbDJjbHVzdGVyIDwtIEZSbWF0Y2hfY2VsbDJjbHVzdGVyKHNjZS5xdWVyeS5ub3JtLCBzY2UucmVmLnNjYWxlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIuc2l6ZT01LCBzdWJzYW1wLnNpemU9MTAsIHVzZS5jb3NpbmU9VFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVmaXg9Yygic3M0LiIsInRlbnguIikpCmBgYAoKVGhlIGVhc2llc3Qgd2F5IHRvIGxvb2sgYXQgdGhlIHJlc3VsdHMgaXMgdG8gdXNlIGl0cyBwbG90IGZ1bmN0aW9uLCB3aGljaCBzaG93cyB0aGUgcHJvcG9ydGlvbiBvZiBxdWVyeSBjZWxscyB0aGF0IGFyZSBtYXRjaGVkIHRvIHRoZSByZWZlcmVuY2UgY2x1c3Rlci4gTm90ZSB0aGF0IHRoZSBjb2x1bW4gc3VtIGlzIDEuCmBgYHtyLCBmaWcud2lkdGg9NC41LCBmaWcuaGVpZ2h0PTR9CnBsb3RfRlJtYXRjaF9jZWxsMmNsdXN0ZXIocnN0LmNlbGwyY2x1c3RlciwgdHlwZT0ibWF0Y2gucHJvcCIpCmBgYAoKTWF0Y2hpbmcgcmVzdWx0cyBvZiBlYWNoIHF1ZXJ5IGNlbGwgY2FuIGJlIGFjY2Vzc2VkIGJlbG93LgpgYGB7cn0KcnN0LmNlbGwyY2x1c3RlciRjZWxsMmNsdXN0ZXIKYGBgCgoKIyMgQ2x1c3Rlci10by1jbHVzdGVyIG1hdGNoaW5nCgpUaG91Z2ggaXQgaXMgc3VnZ2VzdGVkIHRvIHVzZSBzbGlnaHRseSBsYXJnZXIgc3Vic2FtcGxpbmcgc2l6ZSAoYHN1YnNhbXAuc2l6ZT0yMGAgYnkgZGVmYXVsdCkgZm9yIHRoZSBjbHVzdGVyLXRvLWNsdXN0ZXIgbWF0Y2hpbmcuIEhlcmUsIHdlIHVzZSBgc3Vic2FtcC5zaXplPTEwYCBhZ2FpbiBpbiB0aGlzIGV4YW1wbGUuIFlvdXIgYmVzdCBzZXR0aW5nIG1heSBkZXBlbmQgb24geW91ciBkYXRhLCB0aGVyZWZvcmUgd2UgcmVjb21tZW5kIHRvIGNoZWNrIHRoZSBjbHVzdGVyIHNpemVzIGluIHRoZSBiZWdpbm5pbmcuCgpgYGB7ciwgZXZhbD1GQUxTRX0KcnN0IDwtIEZSbWF0Y2goc2NlLnF1ZXJ5Lm5vcm0sIHNjZS5yZWYuc2NhbGUsIAogICAgICAgICAgICAgICBmaWx0ZXIuc2l6ZT01LCBzdWJzYW1wLnNpemU9MTAsIHVzZS5jb3NpbmU9VFJVRSwKICAgICAgICAgICAgICAgcHJlZml4PWMoInNzNC4iLCJ0ZW54LiIpKQpgYGAKClZpc3VhbGl6ZSB0aGUgcmVzdWx0cy4gCmBgYHtyLCBmaWcud2lkdGg9MywgZmlnLmhlaWdodD0yLjV9CnBsb3RfRlJtYXRjaChyc3QpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0zLCBmaWcuaGVpZ2h0PTJ9CnBsb3RfRlJtYXRjaChyc3QsIHR5cGU9InBhZGoiKQpgYGAKClRoZSBwcmVkaWN0aW9uIGZ1bmN0aW9uIGJlbG93IHJldHVybnMgdGhlIG1vc3Qgc2ltaWxhciByZWZlcmVuY2UgY2x1c3RlciBmb3IgZWFjaCBxdWVyeSBjbHVzdGVyLCB0aG91Z2ggc29tZSBhZGp1c3RlZCBwLXZhbHVlcyBtYXkgYmUgYmVsb3cgdGhlIHRocmVzaG9sZCAoaW5kaWNhdGluZyBub3QgYSBmaXJtIG1hdGNoKS4KYGBge3J9CnByZWRpY3RfbW9zdF9zaW1pbGFyX2NsdXN0ZXIocnN0KQpgYGAKCgo=