1 Normalization

Systematic differences in sequencing coverage between libraries are often observed in single-cell RNA sequencing data. They typically arise from technical differences in cDNA capture or PCR amplification efficiency across cells, attributable to the difficulty of achieving consistent library preparation with minimal starting material. Normalization aims to remove these differences such that they do not interfere with comparisons of the expression profiles between cells. This ensures that any observed heterogeneity or differential expression within the cell population is driven by biology and not technical biases.

We will now calculate some properties and visually inspect the data. Our main interest is in the general trends not in individual outliers. Neither genes nor cells that stand out are important at this step, but we focus on the global trends.

Derive gene and cell attributes from the UMI matrix to plot the mean-variance relationship (on the expressed gene and non-empty droplets in log scale).

wget http://perso.ens-lyon.fr/laurent.modolo/scrna/sce_quality_control.Rdata
load(file = "sce_quality_control.Rdata", v = T)
Loading objects:
  sce
sce_hg <- sce[rowData(sce)$expressed & rowData(sce)$species %in% "Homo sapiens",
              colData(sce)$is_cell & colData(sce)$species %in% "Homo sapiens"]
rowData(sce_hg) <- tibble(
  mean = rowMeans(counts(sce_hg)),
  var = apply(counts(sce_hg), 1, var),
  detection_rate = rowMeans(counts(sce_hg) > 0),
  log_mean = log10(mean),
  log_var = log10(var)) %>% 
  as_tibble() %>% 
  cbind(rowData(sce_hg), .)

sce_hg <- sce_hg[rowData(sce_hg)$detection_rate > 0, ]

rowData(sce_hg) %>% 
  as_tibble() %>% 
  ggplot(aes(log_mean, log_var)) +
  geom_point(alpha = 0.3, shape = 16) +
  geom_density_2d(size = 0.3) +
  geom_abline(intercept = 0,
              slope = 1,
              color = 'red')

From this mean-variance relationship, we can see that up to a given mean UMI count the variance follows the line through the origin with slop one, i.e. variance and mean are roughly equal as expected under a Poisson model. However, genes with a higher average UMI count show overdispersion compared to Poisson.

The sampling process of each gene depending on its mean expression can be modeled with a Poisson. You can plot this theoretical detection rate and compare it to the empirical detection rate of each gene.

# add the expected detection rate under Poisson model
x = seq(from = -3, to = 2, length.out = 1000)
poisson_model <- tibble(
  log_mean = x,
  detection_rate = 1 - dpois(0, lambda = 10 ^ x)
)
rowData(sce_hg) %>% 
  as_tibble() %>% 
  dplyr::filter(is_genomic) %>% 
  ggplot(aes(x = log_mean, y = detection_rate)) + 
  geom_point(alpha = 0.3, shape = 16) + 
  geom_line(data = poisson_model, color = 'red') +
  theme_gray(base_size = 8) +
  facet_wrap(~species)

From the mean-detection-rate relationship, we see a lower than expected detection rate in the medium expression range. However, for the highly expressed genes, the rate is at or very close to 1.0 suggesting that there is no zero-inflation in the counts for those genes and that zero-inflation is a result of overdispersion, rather than an independent systematic bias.

colData(sce_hg) %>% 
  as_tibble() %>% 
  ggplot(aes(n_umi, detected)) + 
  geom_point(alpha = 0.3, shape = 16) + 
  geom_density_2d(size = 0.3)

The more UMI counts a cell has, the more genes are detected. In this data set, this seems to be an almost linear relationship (at least within the UMI range of most of the cells).

The scTransform transform algorithms, Hafemeister et al. 2019 propose to model the expression of each gene as a negative binomial random variable with a mean that depends on other variables. Here the other variables can be used to model the differences in sequencing depth between cells and are used as independent variables in a regression model. In order to avoid overfitting, we will first fit model parameters per gene, and then use the relationship between gene mean and parameter values to fit parameters, thereby combining information across genes. Given the fitted model parameters, we transform each observed UMI count into a Pearson residual which can be interpreted as the number of standard deviations an observed count was away from the expected mean. If the model accurately describes the mean-variance relationship and the dependency of mean and latent factors, then the result should have mean zero and a stable variance across the range of expression.

The vst() function estimates model parameters and performs the variance stabilizing transformation. Here we use the log10 of the total UMI counts of a cell as variable for sequencing depth for each cell. After data transformation we plot the model parameters as a function of gene mean (geometric mean). The correct() function computes the corrected count matrix.

set.seed(44)
colData(sce_hg)$log_umi <- log10(colData(sce_hg)$n_umi)
vst_out <- sctransform::vst(
  counts(sce_hg),
  cell_attr = colData(sce_hg),
  latent_var = c("log_umi"),
  return_gene_attr = T,
  return_cell_attr = T,
  show_progress = F)
sctransform::plot_model_pars(vst_out)

sctransform::correct(vst_out, show_progress = F)

Internally vst performs Poisson regression per gene with \(\log(\mu) = \beta_0 + \beta_1 x\), where \(x\) is log_umi, the base 10 logarithm of the total number of UMI counts in each cell, and μ are the expected number of UMI counts of the given gene. The previous plot shows \(\beta_0\) (Intercept), the \(\beta_1\) coefficient log_umi, and the maximum likelihood estimate of the overdispersion parameter theta under the negative binomial model. Under the negative binomial model, the variance of a gene depends on the expected UMI counts and theta: \(\mu + \frac{\mu^2}{\theta}\). In a second step, the regularized model parameters are used to turn observed UMI counts into Pearson residuals.

After exploring the problem of empty droplets and doublets. We now turn toward another scRNASeq data set to exploit another aspect of scRNASeq data.

We are now going to run a simple scRNA-Seq analysis with Seurat

LS0tCnRpdGxlOiAic2luZ2xlLWNlbGwgUk5BIHNlcXVlbmNpbmcgYW5hbHlzaXMiCmF1dGhvcjogTGF1cmVudCBNb2RvbG8KZGF0ZTogMjAyMS0yMDIyCm91dHB1dDoKICAgIGh0bWxfbm90ZWJvb2s6CiAgICAgIHRvYzogdHJ1ZQogICAgICB0b2NfZmxvYXQ6CiAgICAgICAgY29sbGFwc2VkOiBmYWxzZQogICAgICAgIHNtb290aF9zY3JvbGw6IGZhbHNlCiAgICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgICB0aGVtZTogc2FuZHN0b25lCiAgICAgIGhpZ2hsaWdodDogcHlnbWVudHMKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZWNobyA9IFQsCiAgd2FybmluZyA9IEYsCiAgbWVzc2FnZSA9IEYsCiAgY2FjaGUgPSBULAogIHJvb3QuZGlyID0gIi4uIiwKICBmaXQud2lkdGggPSAxMCwKICBmaWcuaGVpZ2h0ID0gNSwKICBmaWcucGF0aCA9ICcuL2ltZy8nLAogIGRwaSA9IDEwMCwKICBwcm9ncmVzcyA9IFRSVUUKKQpgYGAKCiMgTm9ybWFsaXphdGlvbgoKU3lzdGVtYXRpYyBkaWZmZXJlbmNlcyBpbiBzZXF1ZW5jaW5nIGNvdmVyYWdlIGJldHdlZW4gbGlicmFyaWVzIGFyZSBvZnRlbiBvYnNlcnZlZCBpbiBzaW5nbGUtY2VsbCBSTkEgc2VxdWVuY2luZyBkYXRhLiBUaGV5IHR5cGljYWxseSBhcmlzZSBmcm9tIHRlY2huaWNhbCBkaWZmZXJlbmNlcyBpbiBjRE5BIGNhcHR1cmUgb3IgUENSIGFtcGxpZmljYXRpb24gZWZmaWNpZW5jeSBhY3Jvc3MgY2VsbHMsIGF0dHJpYnV0YWJsZSB0byB0aGUgZGlmZmljdWx0eSBvZiBhY2hpZXZpbmcgY29uc2lzdGVudCBsaWJyYXJ5IHByZXBhcmF0aW9uIHdpdGggbWluaW1hbCBzdGFydGluZyBtYXRlcmlhbC4gTm9ybWFsaXphdGlvbiBhaW1zIHRvIHJlbW92ZSB0aGVzZSBkaWZmZXJlbmNlcyBzdWNoIHRoYXQgdGhleSBkbyBub3QgaW50ZXJmZXJlIHdpdGggY29tcGFyaXNvbnMgb2YgdGhlIGV4cHJlc3Npb24gcHJvZmlsZXMgYmV0d2VlbiBjZWxscy4gVGhpcyBlbnN1cmVzIHRoYXQgYW55IG9ic2VydmVkIGhldGVyb2dlbmVpdHkgb3IgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gd2l0aGluIHRoZSBjZWxsIHBvcHVsYXRpb24gaXMgZHJpdmVuIGJ5IGJpb2xvZ3kgYW5kIG5vdCB0ZWNobmljYWwgYmlhc2VzLgoKV2Ugd2lsbCBub3cgY2FsY3VsYXRlIHNvbWUgcHJvcGVydGllcyBhbmQgdmlzdWFsbHkgaW5zcGVjdCB0aGUgZGF0YS4gT3VyIG1haW4gaW50ZXJlc3QgaXMgaW4gdGhlIGdlbmVyYWwgdHJlbmRzIG5vdCBpbiBpbmRpdmlkdWFsIG91dGxpZXJzLiBOZWl0aGVyIGdlbmVzIG5vciBjZWxscyB0aGF0IHN0YW5kIG91dCBhcmUgaW1wb3J0YW50IGF0IHRoaXMgc3RlcCwgYnV0IHdlIGZvY3VzIG9uIHRoZSBnbG9iYWwgdHJlbmRzLgoKRGVyaXZlIGdlbmUgYW5kIGNlbGwgYXR0cmlidXRlcyBmcm9tIHRoZSBVTUkgbWF0cml4IHRvIHBsb3QgdGhlIG1lYW4tdmFyaWFuY2UgcmVsYXRpb25zaGlwIChvbiB0aGUgZXhwcmVzc2VkIGdlbmUgYW5kIG5vbi1lbXB0eSBkcm9wbGV0cyBpbiBsb2cgc2NhbGUpLgoKYGBgc2gKd2dldCBodHRwOi8vcGVyc28uZW5zLWx5b24uZnIvbGF1cmVudC5tb2RvbG8vc2NybmEvc2NlX3F1YWxpdHlfY29udHJvbC5SZGF0YQpgYGAKCmBgYHtyIHNjZV9ub3JtX2luc3BlY3Rpb24sIGRlcGVuZHNvbj0ic2V0dXAifQpsb2FkKGZpbGUgPSAic2NlX3F1YWxpdHlfY29udHJvbC5SZGF0YSIsIHYgPSBUKQpzY2VfaGcgPC0gc2NlW3Jvd0RhdGEoc2NlKSRleHByZXNzZWQgJiByb3dEYXRhKHNjZSkkc3BlY2llcyAlaW4lICJIb21vIHNhcGllbnMiLAogICAgICAgICAgICAgIGNvbERhdGEoc2NlKSRpc19jZWxsICYgY29sRGF0YShzY2UpJHNwZWNpZXMgJWluJSAiSG9tbyBzYXBpZW5zIl0Kcm93RGF0YShzY2VfaGcpIDwtIHRpYmJsZSgKICBtZWFuID0gcm93TWVhbnMoY291bnRzKHNjZV9oZykpLAogIHZhciA9IGFwcGx5KGNvdW50cyhzY2VfaGcpLCAxLCB2YXIpLAogIGRldGVjdGlvbl9yYXRlID0gcm93TWVhbnMoY291bnRzKHNjZV9oZykgPiAwKSwKICBsb2dfbWVhbiA9IGxvZzEwKG1lYW4pLAogIGxvZ192YXIgPSBsb2cxMCh2YXIpKSAlPiUgCiAgYXNfdGliYmxlKCkgJT4lIAogIGNiaW5kKHJvd0RhdGEoc2NlX2hnKSwgLikKCnNjZV9oZyA8LSBzY2VfaGdbcm93RGF0YShzY2VfaGcpJGRldGVjdGlvbl9yYXRlID4gMCwgXQoKcm93RGF0YShzY2VfaGcpICU+JSAKICBhc190aWJibGUoKSAlPiUgCiAgZ2dwbG90KGFlcyhsb2dfbWVhbiwgbG9nX3ZhcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4zLCBzaGFwZSA9IDE2KSArCiAgZ2VvbV9kZW5zaXR5XzJkKHNpemUgPSAwLjMpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLAogICAgICAgICAgICAgIHNsb3BlID0gMSwKICAgICAgICAgICAgICBjb2xvciA9ICdyZWQnKQpgYGAKCgpGcm9tIHRoaXMgbWVhbi12YXJpYW5jZSByZWxhdGlvbnNoaXAsIHdlIGNhbiBzZWUgdGhhdCB1cCB0byBhIGdpdmVuIG1lYW4gVU1JIGNvdW50IHRoZSB2YXJpYW5jZSBmb2xsb3dzIHRoZSBsaW5lIHRocm91Z2ggdGhlIG9yaWdpbiB3aXRoIHNsb3Agb25lLCBpLmUuIHZhcmlhbmNlIGFuZCBtZWFuIGFyZSByb3VnaGx5IGVxdWFsIGFzIGV4cGVjdGVkIHVuZGVyIGEgUG9pc3NvbiBtb2RlbC4gSG93ZXZlciwgZ2VuZXMgd2l0aCBhIGhpZ2hlciBhdmVyYWdlIFVNSSBjb3VudCBzaG93IG92ZXJkaXNwZXJzaW9uIGNvbXBhcmVkIHRvIFBvaXNzb24uCgpUaGUgc2FtcGxpbmcgcHJvY2VzcyBvZiBlYWNoIGdlbmUgZGVwZW5kaW5nIG9uIGl0cyBtZWFuIGV4cHJlc3Npb24gY2FuIGJlIG1vZGVsZWQgd2l0aCBhIFBvaXNzb24uIFlvdSBjYW4gcGxvdCB0aGlzIHRoZW9yZXRpY2FsIGRldGVjdGlvbiByYXRlIGFuZCBjb21wYXJlIGl0IHRvIHRoZSBlbXBpcmljYWwgZGV0ZWN0aW9uIHJhdGUgb2YgZWFjaCBnZW5lLgoKYGBge3Igc2NlX25vcm1faW5zcGVjdGlvbl9jYXB1dHVyZSwgZGVwZW5kc29uPSJzY2Vfbm9ybV9pbnNwZWN0aW9uIn0KIyBhZGQgdGhlIGV4cGVjdGVkIGRldGVjdGlvbiByYXRlIHVuZGVyIFBvaXNzb24gbW9kZWwKeCA9IHNlcShmcm9tID0gLTMsIHRvID0gMiwgbGVuZ3RoLm91dCA9IDEwMDApCnBvaXNzb25fbW9kZWwgPC0gdGliYmxlKAogIGxvZ19tZWFuID0geCwKICBkZXRlY3Rpb25fcmF0ZSA9IDEgLSBkcG9pcygwLCBsYW1iZGEgPSAxMCBeIHgpCikKcm93RGF0YShzY2VfaGcpICU+JSAKICBhc190aWJibGUoKSAlPiUgCiAgZHBseXI6OmZpbHRlcihpc19nZW5vbWljKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gbG9nX21lYW4sIHkgPSBkZXRlY3Rpb25fcmF0ZSkpICsgCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMywgc2hhcGUgPSAxNikgKyAKICBnZW9tX2xpbmUoZGF0YSA9IHBvaXNzb25fbW9kZWwsIGNvbG9yID0gJ3JlZCcpICsKICB0aGVtZV9ncmF5KGJhc2Vfc2l6ZSA9IDgpICsKICBmYWNldF93cmFwKH5zcGVjaWVzKQpgYGAKCkZyb20gdGhlIG1lYW4tZGV0ZWN0aW9uLXJhdGUgcmVsYXRpb25zaGlwLCB3ZSBzZWUgYSBsb3dlciB0aGFuIGV4cGVjdGVkIGRldGVjdGlvbiByYXRlIGluIHRoZSBtZWRpdW0gZXhwcmVzc2lvbiByYW5nZS4gSG93ZXZlciwgZm9yIHRoZSBoaWdobHkgZXhwcmVzc2VkIGdlbmVzLCB0aGUgcmF0ZSBpcyBhdCBvciB2ZXJ5IGNsb3NlIHRvIDEuMCBzdWdnZXN0aW5nIHRoYXQgdGhlcmUgaXMgbm8gemVyby1pbmZsYXRpb24gaW4gdGhlIGNvdW50cyBmb3IgdGhvc2UgZ2VuZXMgYW5kIHRoYXQgemVyby1pbmZsYXRpb24gaXMgYSByZXN1bHQgb2Ygb3ZlcmRpc3BlcnNpb24sIHJhdGhlciB0aGFuIGFuIGluZGVwZW5kZW50IHN5c3RlbWF0aWMgYmlhcy4KCmBgYHtyIHNjZV9ub3JtX2luc3BlY3Rpb25fdW1pLCBkZXBlbmRzb249InNjZV9ub3JtX2luc3BlY3Rpb24ifQpjb2xEYXRhKHNjZV9oZykgJT4lIAogIGFzX3RpYmJsZSgpICU+JSAKICBnZ3Bsb3QoYWVzKG5fdW1pLCBkZXRlY3RlZCkpICsgCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMywgc2hhcGUgPSAxNikgKyAKICBnZW9tX2RlbnNpdHlfMmQoc2l6ZSA9IDAuMykKYGBgCgpUaGUgbW9yZSBVTUkgY291bnRzIGEgY2VsbCBoYXMsIHRoZSBtb3JlIGdlbmVzIGFyZSBkZXRlY3RlZC4gSW4gdGhpcyBkYXRhIHNldCwgdGhpcyBzZWVtcyB0byBiZSBhbiBhbG1vc3QgbGluZWFyIHJlbGF0aW9uc2hpcCAoYXQgbGVhc3Qgd2l0aGluIHRoZSBVTUkgcmFuZ2Ugb2YgbW9zdCBvZiB0aGUgY2VsbHMpLgoKVGhlIGBzY1RyYW5zZm9ybWAgdHJhbnNmb3JtIGFsZ29yaXRobXMsIFtIYWZlbWVpc3RlciBldCBhbC4gMjAxOV0oaHR0cHM6Ly93d3cuYmlvcnhpdi5vcmcvY29udGVudC8xMC4xMTAxLzU3NjgyN3YxKSBwcm9wb3NlIHRvIG1vZGVsIHRoZSBleHByZXNzaW9uIG9mIGVhY2ggZ2VuZSBhcyBhIG5lZ2F0aXZlIGJpbm9taWFsIHJhbmRvbSB2YXJpYWJsZSB3aXRoIGEgbWVhbiB0aGF0IGRlcGVuZHMgb24gb3RoZXIgdmFyaWFibGVzLiBIZXJlIHRoZSBvdGhlciB2YXJpYWJsZXMgY2FuIGJlIHVzZWQgdG8gbW9kZWwgdGhlIGRpZmZlcmVuY2VzIGluIHNlcXVlbmNpbmcgZGVwdGggYmV0d2VlbiBjZWxscyBhbmQgYXJlIHVzZWQgYXMgaW5kZXBlbmRlbnQgdmFyaWFibGVzIGluIGEgcmVncmVzc2lvbiBtb2RlbC4gSW4gb3JkZXIgdG8gYXZvaWQgb3ZlcmZpdHRpbmcsIHdlIHdpbGwgZmlyc3QgZml0IG1vZGVsIHBhcmFtZXRlcnMgcGVyIGdlbmUsIGFuZCB0aGVuIHVzZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gZ2VuZSBtZWFuIGFuZCBwYXJhbWV0ZXIgdmFsdWVzIHRvIGZpdCBwYXJhbWV0ZXJzLCB0aGVyZWJ5IGNvbWJpbmluZyBpbmZvcm1hdGlvbiBhY3Jvc3MgZ2VuZXMuIEdpdmVuIHRoZSBmaXR0ZWQgbW9kZWwgcGFyYW1ldGVycywgd2UgdHJhbnNmb3JtIGVhY2ggb2JzZXJ2ZWQgVU1JIGNvdW50IGludG8gYSBQZWFyc29uIHJlc2lkdWFsIHdoaWNoIGNhbiBiZSBpbnRlcnByZXRlZCBhcyB0aGUgbnVtYmVyIG9mIHN0YW5kYXJkIGRldmlhdGlvbnMgYW4gb2JzZXJ2ZWQgY291bnQgd2FzIGF3YXkgZnJvbSB0aGUgZXhwZWN0ZWQgbWVhbi4gSWYgdGhlIG1vZGVsIGFjY3VyYXRlbHkgZGVzY3JpYmVzIHRoZSBtZWFuLXZhcmlhbmNlIHJlbGF0aW9uc2hpcCBhbmQgdGhlIGRlcGVuZGVuY3kgb2YgbWVhbiBhbmQgbGF0ZW50IGZhY3RvcnMsIHRoZW4gdGhlIHJlc3VsdCBzaG91bGQgaGF2ZSBtZWFuIHplcm8gYW5kIGEgc3RhYmxlIHZhcmlhbmNlIGFjcm9zcyB0aGUgcmFuZ2Ugb2YgZXhwcmVzc2lvbi4KClRoZSBgdnN0KClgIGZ1bmN0aW9uIGVzdGltYXRlcyBtb2RlbCBwYXJhbWV0ZXJzIGFuZCBwZXJmb3JtcyB0aGUgdmFyaWFuY2Ugc3RhYmlsaXppbmcgdHJhbnNmb3JtYXRpb24uIEhlcmUgd2UgdXNlIHRoZSBsb2cxMCBvZiB0aGUgdG90YWwgVU1JIGNvdW50cyBvZiBhIGNlbGwgYXMgdmFyaWFibGUgZm9yIHNlcXVlbmNpbmcgZGVwdGggZm9yIGVhY2ggY2VsbC4gQWZ0ZXIgZGF0YSB0cmFuc2Zvcm1hdGlvbiB3ZSBwbG90IHRoZSBtb2RlbCBwYXJhbWV0ZXJzIGFzIGEgZnVuY3Rpb24gb2YgZ2VuZSBtZWFuIChnZW9tZXRyaWMgbWVhbikuIFRoZSBgY29ycmVjdCgpYCBmdW5jdGlvbiBjb21wdXRlcyB0aGUgY29ycmVjdGVkIGNvdW50IG1hdHJpeC4KCmBgYHtyIHNjdHJhbmZvcm1fdnN0LCBkZXBlbmRzb249InNjZV9ub3JtX2luc3BlY3Rpb24ifQpzZXQuc2VlZCg0NCkKY29sRGF0YShzY2VfaGcpJGxvZ191bWkgPC0gbG9nMTAoY29sRGF0YShzY2VfaGcpJG5fdW1pKQp2c3Rfb3V0IDwtIHNjdHJhbnNmb3JtOjp2c3QoCiAgY291bnRzKHNjZV9oZyksCiAgY2VsbF9hdHRyID0gY29sRGF0YShzY2VfaGcpLAogIGxhdGVudF92YXIgPSBjKCJsb2dfdW1pIiksCiAgcmV0dXJuX2dlbmVfYXR0ciA9IFQsCiAgcmV0dXJuX2NlbGxfYXR0ciA9IFQsCiAgc2hvd19wcm9ncmVzcyA9IEYpCnNjdHJhbnNmb3JtOjpwbG90X21vZGVsX3BhcnModnN0X291dCkKYGBgCgpgYGB7ciBzY3RyYW5mb3JtX2NvcnJlY3QsIGRlcGVuZHNvbj0ic2N0cmFuZm9ybV92c3QiLCBldmFsPUZ9CnNjdHJhbnNmb3JtOjpjb3JyZWN0KHZzdF9vdXQsIHNob3dfcHJvZ3Jlc3MgPSBGKQpgYGAKCkludGVybmFsbHkgYHZzdCBwZXJmb3Jtc2AgUG9pc3NvbiByZWdyZXNzaW9uIHBlciBnZW5lIHdpdGggJFxsb2coXG11KSA9IFxiZXRhXzAgKyBcYmV0YV8xIHgkLCB3aGVyZSAkeCQgaXMgYGxvZ191bWlgLCB0aGUgYmFzZSAxMCBsb2dhcml0aG0gb2YgdGhlIHRvdGFsIG51bWJlciBvZiBVTUkgY291bnRzIGluIGVhY2ggY2VsbCwgYW5kIM68IGFyZSB0aGUgZXhwZWN0ZWQgbnVtYmVyIG9mIFVNSSBjb3VudHMgb2YgdGhlIGdpdmVuIGdlbmUuIFRoZSBwcmV2aW91cyBwbG90IHNob3dzICRcYmV0YV8wJCBgKEludGVyY2VwdClgLCB0aGUgJFxiZXRhXzEkIGNvZWZmaWNpZW50IGBsb2dfdW1pYCwgYW5kIHRoZSBtYXhpbXVtIGxpa2VsaWhvb2QgZXN0aW1hdGUgb2YgdGhlIG92ZXJkaXNwZXJzaW9uIHBhcmFtZXRlciB0aGV0YSB1bmRlciB0aGUgbmVnYXRpdmUgYmlub21pYWwgbW9kZWwuIFVuZGVyIHRoZSBuZWdhdGl2ZSBiaW5vbWlhbCBtb2RlbCwgdGhlIHZhcmlhbmNlIG9mIGEgZ2VuZSBkZXBlbmRzIG9uIHRoZSBleHBlY3RlZCBVTUkgY291bnRzIGFuZCB0aGV0YTogJFxtdSArIFxmcmFje1xtdV4yfXtcdGhldGF9JC4gSW4gYSBzZWNvbmQgc3RlcCwgdGhlIHJlZ3VsYXJpemVkIG1vZGVsIHBhcmFtZXRlcnMgYXJlIHVzZWQgdG8gdHVybiBvYnNlcnZlZCBVTUkgY291bnRzIGludG8gUGVhcnNvbiByZXNpZHVhbHMuCgpBZnRlciBleHBsb3JpbmcgdGhlIHByb2JsZW0gb2YgZW1wdHkgZHJvcGxldHMgYW5kIGRvdWJsZXRzLiBXZSBub3cgdHVybiB0b3dhcmQgYW5vdGhlciBzY1JOQVNlcSBkYXRhIHNldCB0byBleHBsb2l0IGFub3RoZXIgYXNwZWN0IG9mIHNjUk5BU2VxIGRhdGEuCgo+IFdlIGFyZSBub3cgW2dvaW5nIHRvIHJ1biBhIHNpbXBsZSBzY1JOQS1TZXEgYW5hbHlzaXMgd2l0aCBTZXVyYXRdKC4vZGF0YV9hbmFseXNpcy5uYi5odG1sKQo=