Objectives

This notebook will demonstrate how to:

  • Import alevin results with tximeta
  • Calculate and examine cell quality measures

We will continue with the Tabula Muris data set that we started with in the previous notebook.

Roadmap: Preprocessing and Import
Roadmap: Preprocessing and Import

Set Up

# tximeta for importing alevin results
library(tximeta)
Warning: replacing previous import 'S4Arrays::makeNindexFromArrayViewport' by
'DelayedArray::makeNindexFromArrayViewport' when loading 'SummarizedExperiment'
# SingleCellExperiment package for organizing our results
library(SingleCellExperiment)
Loading required package: SummarizedExperiment
Loading required package: MatrixGenerics
Loading required package: matrixStats

Attaching package: 'MatrixGenerics'
The following objects are masked from 'package:matrixStats':

    colAlls, colAnyNAs, colAnys, colAvgsPerRowSet, colCollapse,
    colCounts, colCummaxs, colCummins, colCumprods, colCumsums,
    colDiffs, colIQRDiffs, colIQRs, colLogSumExps, colMadDiffs,
    colMads, colMaxs, colMeans2, colMedians, colMins, colOrderStats,
    colProds, colQuantiles, colRanges, colRanks, colSdDiffs, colSds,
    colSums2, colTabulates, colVarDiffs, colVars, colWeightedMads,
    colWeightedMeans, colWeightedMedians, colWeightedSds,
    colWeightedVars, rowAlls, rowAnyNAs, rowAnys, rowAvgsPerColSet,
    rowCollapse, rowCounts, rowCummaxs, rowCummins, rowCumprods,
    rowCumsums, rowDiffs, rowIQRDiffs, rowIQRs, rowLogSumExps,
    rowMadDiffs, rowMads, rowMaxs, rowMeans2, rowMedians, rowMins,
    rowOrderStats, rowProds, rowQuantiles, rowRanges, rowRanks,
    rowSdDiffs, rowSds, rowSums2, rowTabulates, rowVarDiffs, rowVars,
    rowWeightedMads, rowWeightedMeans, rowWeightedMedians,
    rowWeightedSds, rowWeightedVars
Loading required package: GenomicRanges
Loading required package: stats4
Loading required package: BiocGenerics

Attaching package: 'BiocGenerics'
The following objects are masked from 'package:stats':

    IQR, mad, sd, var, xtabs
The following objects are masked from 'package:base':

    anyDuplicated, aperm, append, as.data.frame, basename, cbind,
    colnames, dirname, do.call, duplicated, eval, evalq, Filter, Find,
    get, grep, grepl, intersect, is.unsorted, lapply, Map, mapply,
    match, mget, order, paste, pmax, pmax.int, pmin, pmin.int,
    Position, rank, rbind, Reduce, rownames, sapply, setdiff, table,
    tapply, union, unique, unsplit, which.max, which.min
Loading required package: S4Vectors

Attaching package: 'S4Vectors'
The following object is masked from 'package:utils':

    findMatches
The following objects are masked from 'package:base':

    expand.grid, I, unname
Loading required package: IRanges
Loading required package: GenomeInfoDb
Loading required package: Biobase
Welcome to Bioconductor

    Vignettes contain introductory material; view with
    'browseVignettes()'. To cite Bioconductor, see
    'citation("Biobase")', and for packages 'citation("pkgname")'.

Attaching package: 'Biobase'
The following object is masked from 'package:MatrixGenerics':

    rowMedians
The following objects are masked from 'package:matrixStats':

    anyMissing, rowMedians
# GGPlot2 for the plots
library(ggplot2)

Import single-cell RNA-seq quantification

Directories and files

The data files we will be using for this part of the project are in the data/tabula-muris subdirectory of the scRNA-seq directory where this notebook is located.

The main files we will be using at this stage are the results from our earlier quantification, located in the alevin-quant subdirectory. Rather than just the subset, we will use the full data in order to get a somewhat more realistic view of a 10x data set. This data set is still a few years old though: newer datasets will tend to have more cells!

# main data directory
data_dir <- file.path("data", "tabula-muris")

# reference files
ref_dir <- file.path("data", "reference")

# Path to the single-sample alevin results
alevin_file <- file.path(data_dir, "alevin-quant",
                         "10X_P4_3", "alevin", "quants_mat.gz")

# Mitochondrial gene table
mito_file <- file.path(ref_dir,
                       "mm_mitochondrial_genes.tsv")

# create the output directory using fs::dir_create()
filtered_dir <- file.path(data_dir, "filtered")
fs::dir_create(filtered_dir)

# Output file
filtered_sce_file <- file.path(filtered_dir, "filtered_sce.rds")

Importing alevin results with tximeta

tximeta needs a data frame with at least these two columns: - a files column with the file paths to the quant.mat.gz files - a names column with the sample names

In this case, we are only importing a single experiment, so we will create a data frame with only one row.

coldata <- data.frame(files = alevin_file,
                      names = "10X_P4_3")

Using the coldata data frame that we set up, we can now run the tximeta() to import our expression data while automatically finding and associating the transcript annotations that were used when we performed the quantification.

The first time you run tximeta() you may get a message about storing downloaded transcriptome data in a cache directory so that it can retrieve the data more quickly the next time. We recommend you use the cache, and accept the default location.

# Read in alevin results with tximeta
bladder_sce <- tximeta(coldata, type = "alevin")
importing quantifications
reading in alevin gene-level counts across cells with 'eds'
found matching transcriptome:
[ Ensembl - Mus musculus - release 95 ]
useHub=TRUE: checking for EnsDb via 'AnnotationHub'
found matching EnsDb via 'AnnotationHub'
downloading 1 resources
retrieving 1 resource
loading from cache
require("ensembldb")
generating gene ranges
generating gene ranges

A quick aside! When we ran alevinQC on this data in the last notebook, we saw that salmon alevin had identified a “whitelist” of barcodes that passed its quality control standards. We could use this filtered list directly, but salmon alevin can be quite strict, and methods for filtering quite variable. Instead, we will use the default behavior of tximeta() and read in all of the barcodes for which there is a non-zero UMI count (after barcode correction). If you wanted instead to include only only barcodes that passed salmon alevin’s filter, you could supply the additional argument alevinArgs = list(filterBarcodes=TRUE) to the tximeta() function. Even if you do choose to read in pre-filtered data, it’s still important to explore the data as we’re about to do here and potentially filter further based on your observations, in particular since mapping software’s quality control measures (spoilers!) don’t always filter based on mitochondrial gene content.

In the intro-to-R-tidyverse module notebook, 01-intro-to-base_R.Rmd, we discuss base R object types, but there are some ‘special’ object types that are package-specific. tximeta creates a SummarizedExperiment object (or more specifically a RangedSummarizedExperiment object), which is used by many Bioconductor packages to store and process results from gene expression studies.

# Explore the SummarizedExperiment data
bladder_sce
class: RangedSummarizedExperiment 
dim: 35429 344 
metadata(6): tximetaInfo quantInfo ... txomeInfo txdbInfo
assays(1): counts
rownames(35429): ENSMUSG00000000001 ENSMUSG00000000003 ...
  ENSMUSG00000117649 ENSMUSG00000117651
rowData names(8): gene_id gene_name ... symbol entrezid
colnames(344): CGGAGTCAGTACGCCC TTGGCAACATGATCCA ... ACGTCAAGTGTAATGA
  ATTACTCAGAGAACAG
colData names(0):

The main component we are concerned with for now is the counts matrix, which is stored as an “assay”, with a row for each gene and a column for each cell. In this case, we can see there is information for 35,429 genes, and Alevin reports data for 344 cells.

tximeta also automatically added some annotation information about each gene, which can be seen by extracting the rowData table.

# Examine row (gene) metadata
rowData(bladder_sce)
DataFrame with 35429 rows and 8 columns
                              gene_id   gene_name         gene_biotype
                          <character> <character>          <character>
ENSMUSG00000000001 ENSMUSG00000000001       Gnai3       protein_coding
ENSMUSG00000000003 ENSMUSG00000000003        Pbsn       protein_coding
ENSMUSG00000000028 ENSMUSG00000000028       Cdc45       protein_coding
ENSMUSG00000000037 ENSMUSG00000000037       Scml2       protein_coding
ENSMUSG00000000049 ENSMUSG00000000049        Apoh       protein_coding
...                               ...         ...                  ...
ENSMUSG00000117643 ENSMUSG00000117643  AC122453.2 processed_pseudogene
ENSMUSG00000117644 ENSMUSG00000117644  AC108777.1 processed_pseudogene
ENSMUSG00000117646 ENSMUSG00000117646  AC122271.3 processed_pseudogene
ENSMUSG00000117649 ENSMUSG00000117649  AC165087.2 processed_pseudogene
ENSMUSG00000117651 ENSMUSG00000117651  CT485613.6 processed_pseudogene
                   seq_coord_system            description
                        <character>            <character>
ENSMUSG00000000001       chromosome guanine nucleotide b..
ENSMUSG00000000003       chromosome probasin [Source:MGI..
ENSMUSG00000000028       chromosome cell division cycle ..
ENSMUSG00000000037       chromosome Scm polycomb group p..
ENSMUSG00000000049       chromosome apolipoprotein H [So..
...                             ...                    ...
ENSMUSG00000117643       chromosome Wilms tumour 1-assoc..
ENSMUSG00000117644       chromosome gametocyte specific ..
ENSMUSG00000117646       chromosome developmental plurip..
ENSMUSG00000117649       chromosome heterogeneous nuclea..
ENSMUSG00000117651       chromosome NSE1 homolog, SMC5-S..
                         gene_id_version      symbol entrezid
                             <character> <character>   <list>
ENSMUSG00000000001  ENSMUSG00000000001.4       Gnai3    14679
ENSMUSG00000000003 ENSMUSG00000000003.15        Pbsn    54192
ENSMUSG00000000028 ENSMUSG00000000028.15       Cdc45    12544
ENSMUSG00000000037 ENSMUSG00000000037.16       Scml2   107815
ENSMUSG00000000049 ENSMUSG00000000049.11        Apoh    11818
...                                  ...         ...      ...
ENSMUSG00000117643  ENSMUSG00000117643.1  AC122453.2       NA
ENSMUSG00000117644  ENSMUSG00000117644.1  AC108777.1       NA
ENSMUSG00000117646  ENSMUSG00000117646.1  AC122271.3       NA
ENSMUSG00000117649  ENSMUSG00000117649.1  AC165087.2       NA
ENSMUSG00000117651  ENSMUSG00000117651.1  CT485613.6       NA

We could leave the object as it is, but we can unlock some extra functionality by converting this from a SummarizedExperiment object to a SingleCellExperiment, so we will go ahead and do that next. SingleCellExperiment objects are a subtype of SummarizedExperiment objects that a lot of single-cell analysis R packages use, so we will try to get acquainted with them.

For more information on SingleCellExperiment objects, as well as many other topics related to this course, we highly recommend the e-book Orchestrating Single-Cell Analysis with Bioconductor (OSCA) and/or Amezquita et al. (2020).

Below is a figure from OSCA that shows the general structure of SingleCellExperiment objects.

Note that three are slots for raw data, metadata about cells, metadata about genes or features, and slots for various transformations of the input data. Many of these will not be filled in when we first create the object, but as we proceed through the workshop we will add in more data to these slots as we compute new summaries and transformations.

To perform the conversion to a SingleCellExperiment, we will use the R function as(), which “coerces” objects from one type to another.

# Convert the SummarizedExperiment to a SingleCellExperiment
bladder_sce <- as(bladder_sce, "SingleCellExperiment")
bladder_sce
class: SingleCellExperiment 
dim: 35429 344 
metadata(6): tximetaInfo quantInfo ... txomeInfo txdbInfo
assays(1): counts
rownames(35429): ENSMUSG00000000001 ENSMUSG00000000003 ...
  ENSMUSG00000117649 ENSMUSG00000117651
rowData names(8): gene_id gene_name ... symbol entrezid
colnames(344): CGGAGTCAGTACGCCC TTGGCAACATGATCCA ... ACGTCAAGTGTAATGA
  ATTACTCAGAGAACAG
colData names(0):
reducedDimNames(0):
mainExpName: NULL
altExpNames(0):

Doing this added a couple of (currently empty) slots for things like dimensionality reduction results and alternative feature experiments. Foreshadowing!

Summarizing expression

For a first pass at the data, we will extract just the counts matrix from the SingleCellExperiment object, and use some base R functions to look at our results.

We can extract the gene by cell count matrix using the counts() function. This actually returns a special format of matrix called a “sparse” matrix. Since single cell count data is mostly zeros, this format (a dgCMatrix object) allows R to save a lot of memory. This object takes up about 6.4 MB, but if we stored it in the normal format, it would be closer to 100 MB! Thankfully, most of the functions that we use to work with regular matrices work just fine with these as well.

sc_counts <- counts(bladder_sce)

Let’s look at the mean expression of the genes in this dataset. We will use apply() in order to calculate things across our data frame. The second argument in apply() specifies whether we are calculating by rows or columns. (1 = rows, 2 = columns).

In the code chunk below, use apply() with the correct arguments to calculate the gene means.

# Let's calculate the gene means (by row)
gene_means <- apply(sc_counts, 1, mean)

This works just fine, but you may have noticed it is a bit slow. For a few common summary functions like means and sums, R has much more efficient functions to calculate across rows or columns. In this case, we can use rowMeans() to do the same calculation much more quickly.

# use rowMeans() to calculate gene means
gene_means <- rowMeans(sc_counts)

Let’s make our first density plot with these data. We will use ggplot() as you have seen before, but since the object we want to plot, gene_means, is a vector not a data frame, we will skip the data argument and go straight to the mapping aesthetics. The remainder of the ggplot code should look familiar.

# Plot the density of the means using ggplot2
ggplot(mapping = aes(x = gene_means)) +
  geom_density() +
  labs(x = "Mean gene count")

That plot is not quite as informative as we might like, as a few genes with high expression are making the scale just a bit wide. Lets zoom in on the left part of the graph by adding an xlim() argument. (Note that xlim() will remove points outside the specified range, so you will get a warning.)

# Plot the density of the means using ggplot2
ggplot(mapping = aes(x = gene_means)) +
  geom_density() +
  labs(x = "Mean gene count") +
  xlim(0, 5)
Warning: Removed 203 rows containing non-finite outside the scale range
(`stat_density()`).

Even as we zoom in, the counts data has many zeroes, which we actually expect in a single cell RNA-seq experiment.

Let’s calculate what proportion of the count data is zeros:

sum(sc_counts == 0)/(nrow(sc_counts) * ncol(sc_counts))
[1] 0.9447591

Quality control measures for the counts matrix

The small amount of RNA in a single cell results in higher chances of errors and biases in RNA isolation, amplification, and sequencing. We should check that the overall data we observe for each sample/cell are reasonable before proceeding too far.

The next section explores some of the ways we can filter the data set to clean things up before we continue to downstream analysis.

QC and filtering
QC and filtering

Total counts as a quality measure

First, lets look at the total number of counts per cell, across all genes. For this we will use colSums(), as each column represents a different sampled cell.

# Make a vector of total_counts number of counts per sample using colSums()
total_counts <- colSums(sc_counts)
# Take a look at the summary statistics for the total counts
summary(total_counts)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    1.0   287.8  3971.5  8089.9 12008.8 62446.0 

Yikes, at least one of the cells has only 1 read!, compared to the median of ~4000! It’s highly likely that this ‘cell’ is either an empty well or did not get sequenced properly.

Let’s visualize the distribution of total counts to see if this is the only cell we might want to exclude.

In following graphs, we will use vertical red lines to indicate possible cutoffs.

# Let's use the same kind of plot as above but add more layers
ggplot(mapping = aes(x = total_counts)) +
  geom_density(fill = "lightblue") +
  geom_vline(xintercept = 1000, color = "red") +
  labs(x = "Counts per cell")

How many cells would be removed with this (or other cutoffs) for counts per sample?

# Calculate the number of cells that would be removed with a given cutoff
count_cutoff <- 1000
sum(total_counts <= count_cutoff)
[1] 133

Number of genes a cell expressed as a quality measure

What if a single gene accounted for all counts in a particular cell? This cell would not have helpful data for us, so we should look to remove any cells we suspect might not have a useful amount of its transcriptome measured.

But before we can determine how many genes we consider a particular cell to be expressing we need to determine a numeric cutoff for what we consider to be a detected gene. How many counts must there be for you to consider a gene expressed? Here let’s go for a simple detection cutoff of > 0.

# make a detection_mat matrix that is TRUE when a gene is expressed in a sample
detection_mat <- sc_counts > 0

Now that we have turned our data into a matrix of TRUE/FALSE for detection, we can sum this data by column to effectively get a vector of how many genes were measured in each cell.

# Make a vector that contains the number of genes expressed by a particular cell
num_genes_exp <- colSums(detection_mat)

Let’s plot this using the same style and type of graph as above.

ggplot(mapping = aes(x = num_genes_exp)) +
  geom_density(fill = "lightblue") +
  labs(x = "Number of genes expressed") +
  theme_classic()

This plot helps us visualize the distribution of genes per cell and can help inform how we choose the cutoff. It’s important to remember that different cell types can have quite different patterns with regards to number of genes expressed. If we were to use strict cutoffs to select which cells are “valid”, there is the possibility that we could bias our results, so this is something we want to be careful about.

Let’s see what happens if we only keep cells with > 500 expressed genes. Just like when we looked at total counts, we can add in a vertical line to the previous plot where the possible cutoff would be.

ggplot(mapping = aes(x = num_genes_exp)) +
  geom_density(fill = "lightblue") +
  labs(x = "Number of genes expressed") +
  theme_classic() +
  geom_vline(xintercept = 500, color = "red")

How many cells would be removed with this cutoff?

# Calculate the number of cells that would be removed with a given cutoff
gene_cutoff <- 500
sum(num_genes_exp <= gene_cutoff)
[1] 145

Mitochondrial gene expression

If a cell is dead or dying, its mRNA will tend to leak out of the cell, leaving an overabundance of mitochondrial RNA, which is more likely to stay within the mitochondria longer. To look for this, we would like to calculate the fraction of mitochondrial expression for each cell as well. First, we will need a list of the mitochondrial genes, which we have prepared in a tsv file mm_mitochondrial_genes.tsv that we will now read in, and filter to just the genes that are found in the data set.

# read `mm_mitochondrial_genes.tsv` from ref_dir and
# create from it a single vector containing only the gene ids
mito_genes <- readr::read_tsv(mito_file) |>
  # filter to only gene in the sce object
  dplyr::filter(gene_id %in% rownames(bladder_sce)) |>
  # pull takes this column out of the data frame as a stand-alone vector
  dplyr::pull(gene_id)
Rows: 37 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (9): gene_id, gene_name, seqnames, strand, gene_biotype, seq_coord_syste...
dbl (4): start, end, width, entrezid

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Now we can use the genes from that list to select only the rows of the count matrix that correspond to the mitochondrial genes and sum their expression for each sample.

# create a mito_rows vector that is TRUE for mitochondrial genes in our dataset
mito_rows <- rownames(sc_counts) %in% mito_genes

# sum the counts from just those genes for all samples
mito_counts <- colSums(sc_counts[mito_rows, ])

# calculate mito_fraction for all samples
mito_fraction <- mito_counts/total_counts

Lets make a plot of this distribution as well!

ggplot(mapping = aes(x = mito_fraction)) +
  geom_density(fill = "lightblue") +
  labs(x = "Mitchondrial fraction") +
  geom_vline(xintercept = 0.2, color = "red") +
  theme_classic()

Here, we want to keep cells with a low fraction of reads corresponding to mitochondrial genes and remove any cells with a high mitochondrial fraction. Again, it’s important to take this step even if you started with filtered data, since mapping software like salmon alevin and Cell Ranger do not usually consider mitochondrial read percentages when filtering.

Combining sample QC measures

Lets put all of the QC measures we have calculated into a single data frame, so we can look at how they might relate to one another.

# make a data frame with number of genes expressed, total counts, and mito fraction
qc_df <- data.frame(barcode = names(num_genes_exp),
                    genes_exp = num_genes_exp,
                    total_counts = total_counts,
                    mito_fraction = mito_fraction)

Now we can plot these measures all together, along with some possible cutoffs.

ggplot(qc_df, aes (x = total_counts,
                   y = genes_exp,
                   color = mito_fraction)) +
  geom_point(alpha = 0.5) +
  scale_color_viridis_c() +
  geom_vline(xintercept = 1000, color = "red") +
  geom_hline(yintercept = 500, color = "red") +
  labs(x = "Total Count",
       y = "Number of Genes Expressed",
       color = "Mitochondrial\nFraction") +
  theme_bw()

If we want to filter our data based on these measures and cutoffs we like, we can do this with dplyr::filter() and then select the resulting columns from the matrix.

# create a filtered_samples data frame from qc_df
filtered_samples <- qc_df |>
  dplyr::filter(total_counts > 1000,
                genes_exp > 500,
                mito_fraction < 0.2)
# select only passing samples for bladder_sce_filtered
sc_counts_filtered <- sc_counts[, filtered_samples$barcode]

Filtering the SingleCellExperiment directly

Calculating cell QC stats with scater

The methods above were nice for demonstrating the kinds of filtering we might do, but all the steps would certainly be repetitive if we had to do them for each sample. Thankfully, there are some nice methods that have been developed in packages like scater to perform them all at once and add the results to the SingleCellExperiment object. The advantages of using functions like this are that we can keep all of the metadata together, filter directly on the object of interest, avoid a lot of repetition, and in doing so avoid many potential errors.

We will start with the function addPerCellQC(), which takes a SingleCellExperiment and a list of gene sets that that we might want to calculate subset information for. In our case, we will just look at mitochondrial genes again.

bladder_sce <- scater::addPerCellQC(
  bladder_sce,
  # a list of named gene subsets that we want stats for
  # here we are using mitochondrial genes
  subsets = list(mito = mito_genes)
  )

The results of these calculations are now stored as a data frame in the colData slot of the SingleCellExperiment object, which we can pull out with the colData() function. (Unfortunately, it is not quite a regular data frame, but we can easily convert it to one.) Even nicer, we can access the QC data in those columns directly with just the familiar $ syntax!

The calculated statistics include sum, the total UMI count for the cell, detected, the number of genes detected, and a few different statistics for each subset that we gave, including the percent (not fraction!) of all UMIs from the subset. Since the subset we used was named mito, this column is called subsets_mito_percent.

Using these, we can recreate the plot from before:

# extract the column data and convert to a data frame
bladder_qc <- data.frame(colData(bladder_sce))

# plot with the qc data frame
ggplot(bladder_qc, aes (x = sum,
                        y = detected,
                       color = subsets_mito_percent)) +
  geom_point(alpha = 0.5) +
  scale_color_viridis_c() +
  labs(x = "Total Count",
       y = "Number of Genes Expressed",
       color = "Mitochondrial\nFraction") +
  theme_bw()

Applying a filter to a SingleCellExperiment

Filtering the SingleCellExperiment object is done as if it were just the counts matrix, with brackets and indexes. While this will look much like what we did before, it is better, because it will also keep the filtered QC stats alongside, in case we wanted to revisit them later. Otherwise, we would have to filter our QC results separately, which is an easy place for errors to creep in.

# create a boolean vector of QC filters
cells_to_keep <- bladder_sce$sum > 1000 &
  bladder_sce$detected > 500 &
  bladder_sce$subsets_mito_percent < 20

# filter the sce object (cells are columns)
bladder_sce_filtered <- bladder_sce[, cells_to_keep]

Just to check, we should have the same number of cells in bladder_sce_filtered as our previous sc_counts_filtered.

ncol(sc_counts_filtered) == ncol(bladder_sce_filtered)
[1] TRUE

Number of cells that express a gene as a quality measure

Now we have an idea of what cells we probably want to get rid of. But what if our data contains genes that we can’t reliably measure in these cells?

We could use our earlier detection_mat to add up how many cells express each gene, but we will skip straight to the scater function this time, which is called addPerFeatureQC(). This will add QC statistics to the rowData for each gene (alongside the annotation data we already had there) The columns it adds are the average expression level of each gene (mean) and the percentage of cells in which it was detected (detected).

bladder_sce_filtered <- scater::addPerFeatureQC(bladder_sce_filtered)

Let’s make another density plot with the percentage of samples that express each gene:

# extract the gene information with
gene_info <- data.frame(rowData(bladder_sce_filtered))

# Plot the detected percentage
ggplot(gene_info, aes(x = detected) )+
  geom_density(fill = "lightblue") +
  labs(x = "Percent of Cells Expressing Each Gene") +
  theme_classic()

How many genes will be excluded if we draw our cutoff at 5% of cells?

sum(gene_info$detected < 5)
[1] 23960

That’s a lot! How do we feel about that?

cutoff <- 2
# filter bladder_sce_filtered to only genes above a cutoff value
bladder_sce_filtered <- bladder_sce_filtered[gene_info$detected >= cutoff, ]

How big is the SingleCellExperiment object now?

dim(bladder_sce_filtered)
[1] 13648   186

Save the filtered data

We will save the filtered SingleCellExperiment object as a .rds file for later use.

# Save object to the file filtered_sce_file, which
# we defined at the top of this notebook
readr::write_rds(bladder_sce_filtered, file = filtered_sce_file)
LS0tCnRpdGxlOiAiU2luZ2xlIGNlbGwgUk5BLXNlcSBxdWFsaXR5IGNvbnRyb2wgYW5kIGZpbHRlcmluZyIKYXV0aG9yOiBDQ0RMIGZvciBBTFNGCmRhdGU6IDIwMjEKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQotLS0KCiMjIE9iamVjdGl2ZXMKClRoaXMgbm90ZWJvb2sgd2lsbCBkZW1vbnN0cmF0ZSBob3cgdG86CgotIEltcG9ydCBhbGV2aW4gcmVzdWx0cyB3aXRoIGB0eGltZXRhYAotIENhbGN1bGF0ZSBhbmQgZXhhbWluZSBjZWxsIHF1YWxpdHkgbWVhc3VyZXMKCi0tLQoKV2Ugd2lsbCBjb250aW51ZSB3aXRoIHRoZSBUYWJ1bGEgTXVyaXMgZGF0YSBzZXQgdGhhdCB3ZSBzdGFydGVkIHdpdGggaW4gdGhlIHByZXZpb3VzIG5vdGVib29rLgoKIVtSb2FkbWFwOiBQcmVwcm9jZXNzaW5nIGFuZCBJbXBvcnRdKGRpYWdyYW1zL3JvYWRtYXBfc2luZ2xlX3ByZXByb2Nlc3NfYWxldmluLnBuZykKCiMjIFNldCBVcAoKYGBge3Igc2V0dXB9CiMgdHhpbWV0YSBmb3IgaW1wb3J0aW5nIGFsZXZpbiByZXN1bHRzCmxpYnJhcnkodHhpbWV0YSkKCiMgU2luZ2xlQ2VsbEV4cGVyaW1lbnQgcGFja2FnZSBmb3Igb3JnYW5pemluZyBvdXIgcmVzdWx0cwpsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KQoKIyBHR1Bsb3QyIGZvciB0aGUgcGxvdHMKbGlicmFyeShnZ3Bsb3QyKQpgYGAKCiMjIEltcG9ydCBzaW5nbGUtY2VsbCBSTkEtc2VxIHF1YW50aWZpY2F0aW9uCgoKCiMjIyBEaXJlY3RvcmllcyBhbmQgZmlsZXMKClRoZSBkYXRhIGZpbGVzIHdlIHdpbGwgYmUgdXNpbmcgZm9yIHRoaXMgcGFydCBvZiB0aGUgcHJvamVjdCBhcmUgaW4gdGhlIGBkYXRhL3RhYnVsYS1tdXJpc2Agc3ViZGlyZWN0b3J5IG9mIHRoZSBgc2NSTkEtc2VxYCBkaXJlY3Rvcnkgd2hlcmUgdGhpcyBub3RlYm9vayBpcyBsb2NhdGVkLgoKVGhlIG1haW4gZmlsZXMgd2Ugd2lsbCBiZSB1c2luZyBhdCB0aGlzIHN0YWdlIGFyZSB0aGUgcmVzdWx0cyBmcm9tIG91ciBlYXJsaWVyIHF1YW50aWZpY2F0aW9uLCBsb2NhdGVkIGluIHRoZSBgYWxldmluLXF1YW50YCBzdWJkaXJlY3RvcnkuClJhdGhlciB0aGFuIGp1c3QgdGhlIHN1YnNldCwgd2Ugd2lsbCB1c2UgdGhlIGZ1bGwgZGF0YSBpbiBvcmRlciB0byBnZXQgYSBzb21ld2hhdCBtb3JlIHJlYWxpc3RpYyB2aWV3IG9mIGEgMTB4IGRhdGEgc2V0LgpUaGlzIGRhdGEgc2V0IGlzIHN0aWxsIGEgZmV3IHllYXJzIG9sZCB0aG91Z2g6IG5ld2VyIGRhdGFzZXRzIHdpbGwgdGVuZCB0byBoYXZlIG1vcmUgY2VsbHMhCgpgYGB7ciBmaWxlcGF0aHN9CiMgbWFpbiBkYXRhIGRpcmVjdG9yeQpkYXRhX2RpciA8LSBmaWxlLnBhdGgoImRhdGEiLCAidGFidWxhLW11cmlzIikKCiMgcmVmZXJlbmNlIGZpbGVzCnJlZl9kaXIgPC0gZmlsZS5wYXRoKCJkYXRhIiwgInJlZmVyZW5jZSIpCgojIFBhdGggdG8gdGhlIHNpbmdsZS1zYW1wbGUgYWxldmluIHJlc3VsdHMKYWxldmluX2ZpbGUgPC0gZmlsZS5wYXRoKGRhdGFfZGlyLCAiYWxldmluLXF1YW50IiwKICAgICAgICAgICAgICAgICAgICAgICAgICIxMFhfUDRfMyIsICJhbGV2aW4iLCAicXVhbnRzX21hdC5neiIpCgojIE1pdG9jaG9uZHJpYWwgZ2VuZSB0YWJsZQptaXRvX2ZpbGUgPC0gZmlsZS5wYXRoKHJlZl9kaXIsCiAgICAgICAgICAgICAgICAgICAgICAgIm1tX21pdG9jaG9uZHJpYWxfZ2VuZXMudHN2IikKCiMgY3JlYXRlIHRoZSBvdXRwdXQgZGlyZWN0b3J5IHVzaW5nIGZzOjpkaXJfY3JlYXRlKCkKZmlsdGVyZWRfZGlyIDwtIGZpbGUucGF0aChkYXRhX2RpciwgImZpbHRlcmVkIikKZnM6OmRpcl9jcmVhdGUoZmlsdGVyZWRfZGlyKQoKIyBPdXRwdXQgZmlsZQpmaWx0ZXJlZF9zY2VfZmlsZSA8LSBmaWxlLnBhdGgoZmlsdGVyZWRfZGlyLCAiZmlsdGVyZWRfc2NlLnJkcyIpCmBgYAoKCiMjIEltcG9ydGluZyBhbGV2aW4gcmVzdWx0cyB3aXRoIHR4aW1ldGEKCmB0eGltZXRhYCBuZWVkcyBhIGRhdGEgZnJhbWUgd2l0aCBhdCBsZWFzdCB0aGVzZSB0d28gY29sdW1uczoKLSBhIGBmaWxlc2AgY29sdW1uICB3aXRoIHRoZSBmaWxlIHBhdGhzIHRvIHRoZSBxdWFudC5tYXQuZ3ogZmlsZXMKLSBhIGBuYW1lc2AgY29sdW1uIHdpdGggdGhlIHNhbXBsZSBuYW1lcwoKSW4gdGhpcyBjYXNlLCB3ZSBhcmUgb25seSBpbXBvcnRpbmcgYSBzaW5nbGUgZXhwZXJpbWVudCwgc28gd2Ugd2lsbCBjcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGggb25seSBvbmUgcm93LgoKYGBge3IgbmFtZXNfc2ZfZmlsZXMsIGxpdmUgPSBUUlVFfQpjb2xkYXRhIDwtIGRhdGEuZnJhbWUoZmlsZXMgPSBhbGV2aW5fZmlsZSwKICAgICAgICAgICAgICAgICAgICAgIG5hbWVzID0gIjEwWF9QNF8zIikKYGBgCgpVc2luZyB0aGUgYGNvbGRhdGFgIGRhdGEgZnJhbWUgdGhhdCB3ZSBzZXQgdXAsIHdlIGNhbiBub3cgcnVuIHRoZSBgdHhpbWV0YSgpYCB0byBpbXBvcnQgb3VyIGV4cHJlc3Npb24gZGF0YSB3aGlsZSBhdXRvbWF0aWNhbGx5IGZpbmRpbmcgYW5kIGFzc29jaWF0aW5nIHRoZSB0cmFuc2NyaXB0IGFubm90YXRpb25zIHRoYXQgd2VyZSB1c2VkIHdoZW4gd2UgcGVyZm9ybWVkIHRoZSBxdWFudGlmaWNhdGlvbi4KClRoZSBmaXJzdCB0aW1lIHlvdSBydW4gYHR4aW1ldGEoKWAgeW91IG1heSBnZXQgYSBtZXNzYWdlIGFib3V0IHN0b3JpbmcgZG93bmxvYWRlZCB0cmFuc2NyaXB0b21lIGRhdGEgaW4gYSBjYWNoZSBkaXJlY3Rvcnkgc28gdGhhdCBpdCBjYW4gcmV0cmlldmUgdGhlIGRhdGEgbW9yZSBxdWlja2x5IHRoZSBuZXh0IHRpbWUuCldlIHJlY29tbWVuZCB5b3UgdXNlIHRoZSBjYWNoZSwgYW5kIGFjY2VwdCB0aGUgZGVmYXVsdCBsb2NhdGlvbi4KCgpgYGB7ciByZWFkX2RhdGEsIGxpdmUgPSBUUlVFfQojIFJlYWQgaW4gYWxldmluIHJlc3VsdHMgd2l0aCB0eGltZXRhCmJsYWRkZXJfc2NlIDwtIHR4aW1ldGEoY29sZGF0YSwgdHlwZSA9ICJhbGV2aW4iKQpgYGAKCkEgcXVpY2sgYXNpZGUhCldoZW4gd2UgcmFuIGBhbGV2aW5RQ2Agb24gdGhpcyBkYXRhIGluIHRoZSBsYXN0IG5vdGVib29rLCB3ZSBzYXcgdGhhdCBgc2FsbW9uIGFsZXZpbmAgaGFkIGlkZW50aWZpZWQgYSAid2hpdGVsaXN0IiBvZiBiYXJjb2RlcyB0aGF0IHBhc3NlZCBpdHMgcXVhbGl0eSBjb250cm9sIHN0YW5kYXJkcy4KV2UgY291bGQgdXNlIHRoaXMgZmlsdGVyZWQgbGlzdCBkaXJlY3RseSwgYnV0IGBzYWxtb24gYWxldmluYCBjYW4gYmUgcXVpdGUgc3RyaWN0LCBhbmQgbWV0aG9kcyBmb3IgZmlsdGVyaW5nIHF1aXRlIHZhcmlhYmxlLgpJbnN0ZWFkLCB3ZSB3aWxsIHVzZSB0aGUgZGVmYXVsdCBiZWhhdmlvciBvZiBgdHhpbWV0YSgpYCBhbmQgcmVhZCBpbiBhbGwgb2YgdGhlIGJhcmNvZGVzIGZvciB3aGljaCB0aGVyZSBpcyBhIG5vbi16ZXJvIFVNSSBjb3VudCAoYWZ0ZXIgYmFyY29kZSBjb3JyZWN0aW9uKS4KSWYgeW91IHdhbnRlZCBpbnN0ZWFkIHRvIGluY2x1ZGUgb25seSBvbmx5IGJhcmNvZGVzIHRoYXQgcGFzc2VkIGBzYWxtb24gYWxldmluYCdzIGZpbHRlciwgeW91IGNvdWxkIHN1cHBseSB0aGUgYWRkaXRpb25hbCBhcmd1bWVudCBgYWxldmluQXJncyA9IGxpc3QoZmlsdGVyQmFyY29kZXM9VFJVRSlgIHRvIHRoZSBgdHhpbWV0YSgpYCBmdW5jdGlvbi4gCkV2ZW4gaWYgeW91IGRvIGNob29zZSB0byByZWFkIGluIHByZS1maWx0ZXJlZCBkYXRhLCBpdCdzIHN0aWxsIGltcG9ydGFudCB0byBleHBsb3JlIHRoZSBkYXRhIGFzIHdlJ3JlIGFib3V0IHRvIGRvIGhlcmUgYW5kIHBvdGVudGlhbGx5IGZpbHRlciBmdXJ0aGVyIGJhc2VkIG9uIHlvdXIgb2JzZXJ2YXRpb25zLCBpbiBwYXJ0aWN1bGFyIHNpbmNlIG1hcHBpbmcgc29mdHdhcmUncyBxdWFsaXR5IGNvbnRyb2wgbWVhc3VyZXMgKHNwb2lsZXJzISkgZG9uJ3QgYWx3YXlzIGZpbHRlciBiYXNlZCBvbiBtaXRvY2hvbmRyaWFsIGdlbmUgY29udGVudC4gCgpJbiB0aGUgaW50cm8tdG8tUi10aWR5dmVyc2UgbW9kdWxlIG5vdGVib29rLCBgMDEtaW50cm8tdG8tYmFzZV9SLlJtZGAsIHdlIGRpc2N1c3MgYmFzZSBSIG9iamVjdCB0eXBlcywgYnV0IHRoZXJlIGFyZSBzb21lICdzcGVjaWFsJyBvYmplY3QgdHlwZXMgdGhhdCBhcmUgcGFja2FnZS1zcGVjaWZpYy4KYHR4aW1ldGFgIGNyZWF0ZXMgYSBgU3VtbWFyaXplZEV4cGVyaW1lbnRgIG9iamVjdCAob3IgbW9yZSBzcGVjaWZpY2FsbHkgYSBgUmFuZ2VkU3VtbWFyaXplZEV4cGVyaW1lbnRgIG9iamVjdCksIHdoaWNoIGlzIHVzZWQgYnkgbWFueSBCaW9jb25kdWN0b3IgcGFja2FnZXMgdG8gc3RvcmUgYW5kIHByb2Nlc3MgcmVzdWx0cyBmcm9tIGdlbmUgZXhwcmVzc2lvbiBzdHVkaWVzLgoKYGBge3Igdmlld19zY2RhdGEsIGxpdmUgPSBUUlVFfQojIEV4cGxvcmUgdGhlIFN1bW1hcml6ZWRFeHBlcmltZW50IGRhdGEKYmxhZGRlcl9zY2UKYGBgCgpUaGUgbWFpbiBjb21wb25lbnQgd2UgYXJlIGNvbmNlcm5lZCB3aXRoIGZvciBub3cgaXMgdGhlIGBjb3VudHNgIG1hdHJpeCwgd2hpY2ggaXMgc3RvcmVkIGFzIGFuICJhc3NheSIsIHdpdGggYSByb3cgZm9yIGVhY2ggZ2VuZSBhbmQgYSBjb2x1bW4gZm9yIGVhY2ggY2VsbC4KSW4gdGhpcyBjYXNlLCB3ZSBjYW4gc2VlIHRoZXJlIGlzIGluZm9ybWF0aW9uIGZvciAzNSw0MjkgZ2VuZXMsIGFuZCBBbGV2aW4gcmVwb3J0cyBkYXRhIGZvciAzNDQgY2VsbHMuCgpgdHhpbWV0YWAgYWxzbyBhdXRvbWF0aWNhbGx5IGFkZGVkIHNvbWUgYW5ub3RhdGlvbiBpbmZvcm1hdGlvbiBhYm91dCBlYWNoIGdlbmUsIHdoaWNoIGNhbiBiZSBzZWVuIGJ5IGV4dHJhY3RpbmcgdGhlIGByb3dEYXRhYCB0YWJsZS4KCmBgYHtyIHZpZXdfYW5ub3RhdGlvbiwgbGl2ZSA9IFRSVUV9CiMgRXhhbWluZSByb3cgKGdlbmUpIG1ldGFkYXRhCnJvd0RhdGEoYmxhZGRlcl9zY2UpCmBgYAoKV2UgY291bGQgbGVhdmUgdGhlIG9iamVjdCBhcyBpdCBpcywgYnV0IHdlIGNhbiB1bmxvY2sgc29tZSBleHRyYSBmdW5jdGlvbmFsaXR5IGJ5IGNvbnZlcnRpbmcgdGhpcyBmcm9tIGEgYFN1bW1hcml6ZWRFeHBlcmltZW50YCBvYmplY3QgdG8gYSBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgLCBzbyB3ZSB3aWxsIGdvIGFoZWFkIGFuZCBkbyB0aGF0IG5leHQuCmBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0cyBhcmUgYSBzdWJ0eXBlIG9mIGBTdW1tYXJpemVkRXhwZXJpbWVudGAgb2JqZWN0cyB0aGF0IGEgbG90IG9mIHNpbmdsZS1jZWxsIGFuYWx5c2lzIFIgcGFja2FnZXMgdXNlLCBzbyB3ZSB3aWxsIHRyeSB0byBnZXQgYWNxdWFpbnRlZCB3aXRoIHRoZW0uCgpGb3IgbW9yZSBpbmZvcm1hdGlvbiBvbiBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIG9iamVjdHMsIGFzIHdlbGwgYXMgbWFueSBvdGhlciB0b3BpY3MgcmVsYXRlZCB0byB0aGlzIGNvdXJzZSwgd2UgaGlnaGx5IHJlY29tbWVuZCB0aGUgZS1ib29rIFtfT3JjaGVzdHJhdGluZyBTaW5nbGUtQ2VsbCBBbmFseXNpcyB3aXRoIEJpb2NvbmR1Y3Rvcl8gKE9TQ0EpXShodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy8zLjE2L09TQ0EvKSBhbmQvb3IgW0FtZXpxdWl0YSAqZXQgYWwuKiAoMjAyMCldKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxNTkyLTAxOS0wNjU0LXgpLgoKQmVsb3cgaXMgYSBmaWd1cmUgZnJvbSBPU0NBIHRoYXQgc2hvd3MgdGhlIGdlbmVyYWwgc3RydWN0dXJlIG9mIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0cy4KCiFbXShkaWFncmFtcy9TaW5nbGVDZWxsRXhwZXJpbWVudC5wbmcpCgpOb3RlIHRoYXQgdGhyZWUgYXJlIHNsb3RzIGZvciByYXcgZGF0YSwgbWV0YWRhdGEgYWJvdXQgY2VsbHMsIG1ldGFkYXRhIGFib3V0IGdlbmVzIG9yIGZlYXR1cmVzLCBhbmQgc2xvdHMgZm9yIHZhcmlvdXMgdHJhbnNmb3JtYXRpb25zIG9mIHRoZSBpbnB1dCBkYXRhLgpNYW55IG9mIHRoZXNlIHdpbGwgbm90IGJlIGZpbGxlZCBpbiB3aGVuIHdlIGZpcnN0IGNyZWF0ZSB0aGUgb2JqZWN0LCBidXQgYXMgd2UgcHJvY2VlZCB0aHJvdWdoIHRoZSB3b3Jrc2hvcCB3ZSB3aWxsIGFkZCBpbiBtb3JlIGRhdGEgdG8gdGhlc2Ugc2xvdHMgYXMgd2UgY29tcHV0ZSBuZXcgc3VtbWFyaWVzIGFuZCB0cmFuc2Zvcm1hdGlvbnMuCgpUbyBwZXJmb3JtIHRoZSBjb252ZXJzaW9uIHRvIGEgYFNpbmdsZUNlbGxFeHBlcmltZW50YCwgd2Ugd2lsbCB1c2UgdGhlIFIgZnVuY3Rpb24gYGFzKClgLCB3aGljaCAiY29lcmNlcyIgb2JqZWN0cyBmcm9tIG9uZSB0eXBlIHRvIGFub3RoZXIuCgpgYGB7ciBjb252ZXJ0X3NjZSwgbGl2ZSA9IFRSVUV9CiMgQ29udmVydCB0aGUgU3VtbWFyaXplZEV4cGVyaW1lbnQgdG8gYSBTaW5nbGVDZWxsRXhwZXJpbWVudApibGFkZGVyX3NjZSA8LSBhcyhibGFkZGVyX3NjZSwgIlNpbmdsZUNlbGxFeHBlcmltZW50IikKYmxhZGRlcl9zY2UKYGBgCgpEb2luZyB0aGlzIGFkZGVkIGEgY291cGxlIG9mIChjdXJyZW50bHkgZW1wdHkpIHNsb3RzIGZvciB0aGluZ3MgbGlrZSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gcmVzdWx0cyBhbmQgYWx0ZXJuYXRpdmUgZmVhdHVyZSBleHBlcmltZW50cy4gRm9yZXNoYWRvd2luZyEKCiMjIFN1bW1hcml6aW5nIGV4cHJlc3Npb24KCkZvciBhIGZpcnN0IHBhc3MgYXQgdGhlIGRhdGEsIHdlIHdpbGwgZXh0cmFjdCBqdXN0IHRoZSBjb3VudHMgbWF0cml4IGZyb20gdGhlIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0LCBhbmQgdXNlIHNvbWUgYmFzZSBSIGZ1bmN0aW9ucyB0byBsb29rIGF0IG91ciByZXN1bHRzLgoKV2UgY2FuIGV4dHJhY3QgdGhlIGdlbmUgYnkgY2VsbCBjb3VudCBtYXRyaXggdXNpbmcgdGhlIGBjb3VudHMoKWAgZnVuY3Rpb24uClRoaXMgYWN0dWFsbHkgcmV0dXJucyBhIHNwZWNpYWwgZm9ybWF0IG9mIG1hdHJpeCBjYWxsZWQgYSAic3BhcnNlIiBtYXRyaXguClNpbmNlIHNpbmdsZSBjZWxsIGNvdW50IGRhdGEgaXMgbW9zdGx5IHplcm9zLCB0aGlzIGZvcm1hdCAoYSBgZGdDTWF0cml4YCBvYmplY3QpIGFsbG93cyBSIHRvIHNhdmUgYSBsb3Qgb2YgbWVtb3J5LgpUaGlzIG9iamVjdCB0YWtlcyB1cCBhYm91dCA2LjQgTUIsIGJ1dCBpZiB3ZSBzdG9yZWQgaXQgaW4gdGhlIG5vcm1hbCBmb3JtYXQsIGl0IHdvdWxkIGJlIGNsb3NlciB0byAxMDAgTUIhClRoYW5rZnVsbHksIG1vc3Qgb2YgdGhlIGZ1bmN0aW9ucyB0aGF0IHdlIHVzZSB0byB3b3JrIHdpdGggcmVndWxhciBtYXRyaWNlcyB3b3JrIGp1c3QgZmluZSB3aXRoIHRoZXNlIGFzIHdlbGwuCgpgYGB7ciBtYWtlX21hdHJpeH0Kc2NfY291bnRzIDwtIGNvdW50cyhibGFkZGVyX3NjZSkKYGBgCgpMZXQncyBsb29rIGF0IHRoZSBtZWFuIGV4cHJlc3Npb24gb2YgdGhlIGdlbmVzIGluIHRoaXMgZGF0YXNldC4KV2Ugd2lsbCB1c2UgYGFwcGx5KClgIGluIG9yZGVyIHRvIGNhbGN1bGF0ZSB0aGluZ3MgYWNyb3NzIG91ciBkYXRhIGZyYW1lLgpUaGUgc2Vjb25kIGFyZ3VtZW50IGluIGBhcHBseSgpYCBzcGVjaWZpZXMgd2hldGhlciB3ZSBhcmUgY2FsY3VsYXRpbmcgYnkgcm93cyBvciBjb2x1bW5zLgooMSA9IHJvd3MsIDIgPSBjb2x1bW5zKS4KCkluIHRoZSBjb2RlIGNodW5rIGJlbG93LCB1c2UgYGFwcGx5KClgIHdpdGggdGhlIGNvcnJlY3QgYXJndW1lbnRzIHRvIGNhbGN1bGF0ZSB0aGUgZ2VuZSBtZWFucy4KCmBgYHtyIG1lYW5zLCBsaXZlID0gVFJVRX0KIyBMZXQncyBjYWxjdWxhdGUgdGhlIGdlbmUgbWVhbnMgKGJ5IHJvdykKZ2VuZV9tZWFucyA8LSBhcHBseShzY19jb3VudHMsIDEsIG1lYW4pCmBgYAoKVGhpcyB3b3JrcyBqdXN0IGZpbmUsIGJ1dCB5b3UgbWF5IGhhdmUgbm90aWNlZCBpdCBpcyBhIGJpdCBzbG93LgpGb3IgYSBmZXcgY29tbW9uIHN1bW1hcnkgZnVuY3Rpb25zIGxpa2UgbWVhbnMgYW5kIHN1bXMsIFIgaGFzIG11Y2ggbW9yZSBlZmZpY2llbnQgZnVuY3Rpb25zIHRvIGNhbGN1bGF0ZSBhY3Jvc3Mgcm93cyBvciBjb2x1bW5zLgpJbiB0aGlzIGNhc2UsIHdlIGNhbiB1c2UgYHJvd01lYW5zKClgIHRvIGRvIHRoZSBzYW1lIGNhbGN1bGF0aW9uIG11Y2ggbW9yZSBxdWlja2x5LgoKCmBgYHtyIHJvd21lYW5zfQojIHVzZSByb3dNZWFucygpIHRvIGNhbGN1bGF0ZSBnZW5lIG1lYW5zCmdlbmVfbWVhbnMgPC0gcm93TWVhbnMoc2NfY291bnRzKQpgYGAKCkxldCdzIG1ha2Ugb3VyIGZpcnN0IGRlbnNpdHkgcGxvdCB3aXRoIHRoZXNlIGRhdGEuCldlIHdpbGwgdXNlIGBnZ3Bsb3QoKWAgYXMgeW91IGhhdmUgc2VlbiBiZWZvcmUsIGJ1dCBzaW5jZSB0aGUgb2JqZWN0IHdlIHdhbnQgdG8gcGxvdCwgYGdlbmVfbWVhbnNgLCBpcyBhIHZlY3RvciBub3QgYSBkYXRhIGZyYW1lLCB3ZSB3aWxsIHNraXAgdGhlIGBkYXRhYCBhcmd1bWVudCBhbmQgZ28gc3RyYWlnaHQgdG8gdGhlIGBtYXBwaW5nYCBhZXN0aGV0aWNzLgpUaGUgcmVtYWluZGVyIG9mIHRoZSBgZ2dwbG90YCBjb2RlIHNob3VsZCBsb29rIGZhbWlsaWFyLgoKYGBge3IgbWVhbl9kZW5zaXR5fQojIFBsb3QgdGhlIGRlbnNpdHkgb2YgdGhlIG1lYW5zIHVzaW5nIGdncGxvdDIKZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGdlbmVfbWVhbnMpKSArCiAgZ2VvbV9kZW5zaXR5KCkgKwogIGxhYnMoeCA9ICJNZWFuIGdlbmUgY291bnQiKQpgYGAKClRoYXQgcGxvdCBpcyBub3QgcXVpdGUgYXMgaW5mb3JtYXRpdmUgYXMgd2UgbWlnaHQgbGlrZSwgYXMgYSBmZXcgZ2VuZXMgd2l0aCBoaWdoIGV4cHJlc3Npb24gYXJlIG1ha2luZyB0aGUgc2NhbGUganVzdCBhICpiaXQqIHdpZGUuCkxldHMgem9vbSBpbiBvbiB0aGUgbGVmdCBwYXJ0IG9mIHRoZSBncmFwaCBieSBhZGRpbmcgYW4gYHhsaW0oKWAgYXJndW1lbnQuCihOb3RlIHRoYXQgYHhsaW0oKWAgd2lsbCByZW1vdmUgcG9pbnRzIG91dHNpZGUgdGhlIHNwZWNpZmllZCByYW5nZSwgc28geW91IHdpbGwgZ2V0IGEgd2FybmluZy4pCgpgYGB7ciB6b29tX2RlbnNpdHksIGxpdmUgPSBUUlVFfQojIFBsb3QgdGhlIGRlbnNpdHkgb2YgdGhlIG1lYW5zIHVzaW5nIGdncGxvdDIKZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGdlbmVfbWVhbnMpKSArCiAgZ2VvbV9kZW5zaXR5KCkgKwogIGxhYnMoeCA9ICJNZWFuIGdlbmUgY291bnQiKSArCiAgeGxpbSgwLCA1KQpgYGAKCkV2ZW4gYXMgd2Ugem9vbSBpbiwgdGhlIGNvdW50cyBkYXRhIGhhcyBtYW55IHplcm9lcywgd2hpY2ggd2UgYWN0dWFsbHkgZXhwZWN0IGluIGEgc2luZ2xlIGNlbGwgUk5BLXNlcSBleHBlcmltZW50LgoKTGV0J3MgY2FsY3VsYXRlIHdoYXQgcHJvcG9ydGlvbiBvZiB0aGUgY291bnQgZGF0YSBpcyB6ZXJvczoKCmBgYHtyIHplcm9fZnJhY3Rpb24sIGxpdmUgPSBUUlVFfQpzdW0oc2NfY291bnRzID09IDApLyhucm93KHNjX2NvdW50cykgKiBuY29sKHNjX2NvdW50cykpCmBgYAoKCiMjIFF1YWxpdHkgY29udHJvbCBtZWFzdXJlcyBmb3IgdGhlIGNvdW50cyBtYXRyaXgKClRoZSBzbWFsbCBhbW91bnQgb2YgUk5BIGluIGEgc2luZ2xlIGNlbGwgcmVzdWx0cyBpbiBoaWdoZXIgY2hhbmNlcyBvZiBlcnJvcnMgYW5kIGJpYXNlcyBpbiBSTkEgaXNvbGF0aW9uLCBhbXBsaWZpY2F0aW9uLCBhbmQgc2VxdWVuY2luZy4KV2Ugc2hvdWxkIGNoZWNrIHRoYXQgdGhlIG92ZXJhbGwgZGF0YSB3ZSBvYnNlcnZlIGZvciBlYWNoIHNhbXBsZS9jZWxsIGFyZSByZWFzb25hYmxlIGJlZm9yZSBwcm9jZWVkaW5nIHRvbyBmYXIuCgpUaGUgbmV4dCBzZWN0aW9uIGV4cGxvcmVzIHNvbWUgb2YgdGhlIHdheXMgd2UgY2FuIGZpbHRlciB0aGUgZGF0YSBzZXQgdG8gY2xlYW4gdGhpbmdzIHVwIGJlZm9yZSB3ZSBjb250aW51ZSB0byBkb3duc3RyZWFtIGFuYWx5c2lzLgoKIVtRQyBhbmQgZmlsdGVyaW5nXShkaWFncmFtcy9yb2FkbWFwX3NpbmdsZV9xY19ub3JtX2FsZXZpbi5wbmcpCgojIyMjIFRvdGFsIGNvdW50cyBhcyBhIHF1YWxpdHkgbWVhc3VyZQoKRmlyc3QsIGxldHMgbG9vayBhdCB0aGUgdG90YWwgbnVtYmVyIG9mIGNvdW50cyBwZXIgY2VsbCwgYWNyb3NzIGFsbCBnZW5lcy4KRm9yIHRoaXMgd2Ugd2lsbCB1c2UgYGNvbFN1bXMoKWAsIGFzIGVhY2ggY29sdW1uIHJlcHJlc2VudHMgYSBkaWZmZXJlbnQgc2FtcGxlZCBjZWxsLgoKYGBge3IgdG90YWxfY291bnRzLCBsaXZlID0gVFJVRX0KIyBNYWtlIGEgdmVjdG9yIG9mIHRvdGFsX2NvdW50cyBudW1iZXIgb2YgY291bnRzIHBlciBzYW1wbGUgdXNpbmcgY29sU3VtcygpCnRvdGFsX2NvdW50cyA8LSBjb2xTdW1zKHNjX2NvdW50cykKYGBgCgoKYGBge3IgY291bnRzX3N1bW1hcnksIGxpdmUgPSBUUlVFfQojIFRha2UgYSBsb29rIGF0IHRoZSBzdW1tYXJ5IHN0YXRpc3RpY3MgZm9yIHRoZSB0b3RhbCBjb3VudHMKc3VtbWFyeSh0b3RhbF9jb3VudHMpCmBgYAoKWWlrZXMsIGF0IGxlYXN0IG9uZSBvZiB0aGUgY2VsbHMgaGFzIG9ubHkgMSByZWFkISwgY29tcGFyZWQgdG8gdGhlIG1lZGlhbiBvZiB+NDAwMCEKSXQncyBoaWdobHkgbGlrZWx5IHRoYXQgdGhpcyAnY2VsbCcgaXMgZWl0aGVyIGFuIGVtcHR5IHdlbGwgb3IgZGlkIG5vdCBnZXQgc2VxdWVuY2VkIHByb3Blcmx5LgoKTGV0J3MgdmlzdWFsaXplIHRoZSBkaXN0cmlidXRpb24gb2YgdG90YWwgY291bnRzIHRvIHNlZSBpZiB0aGlzIGlzIHRoZSBvbmx5IGNlbGwgd2UgbWlnaHQgd2FudCB0byBleGNsdWRlLgoKSW4gZm9sbG93aW5nIGdyYXBocywgd2Ugd2lsbCB1c2UgdmVydGljYWwgcmVkIGxpbmVzIHRvIGluZGljYXRlIHBvc3NpYmxlIGN1dG9mZnMuCgpgYGB7ciB0b3RhbF9jb3VudHNfcGxvdCwgbGl2ZSA9IFRSVUV9CiMgTGV0J3MgdXNlIHRoZSBzYW1lIGtpbmQgb2YgcGxvdCBhcyBhYm92ZSBidXQgYWRkIG1vcmUgbGF5ZXJzCmdncGxvdChtYXBwaW5nID0gYWVzKHggPSB0b3RhbF9jb3VudHMpKSArCiAgZ2VvbV9kZW5zaXR5KGZpbGwgPSAibGlnaHRibHVlIikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDEwMDAsIGNvbG9yID0gInJlZCIpICsKICBsYWJzKHggPSAiQ291bnRzIHBlciBjZWxsIikKYGBgCgpIb3cgbWFueSBjZWxscyB3b3VsZCBiZSByZW1vdmVkIHdpdGggdGhpcyAob3Igb3RoZXIgY3V0b2ZmcykgZm9yIGNvdW50cyBwZXIgc2FtcGxlPwoKYGBge3IgY291bnRfY3V0b2Zmc30KIyBDYWxjdWxhdGUgdGhlIG51bWJlciBvZiBjZWxscyB0aGF0IHdvdWxkIGJlIHJlbW92ZWQgd2l0aCBhIGdpdmVuIGN1dG9mZgpjb3VudF9jdXRvZmYgPC0gMTAwMApzdW0odG90YWxfY291bnRzIDw9IGNvdW50X2N1dG9mZikKYGBgCgoKIyMjIE51bWJlciBvZiBnZW5lcyBhIGNlbGwgZXhwcmVzc2VkIGFzIGEgcXVhbGl0eSBtZWFzdXJlCgpXaGF0IGlmIGEgc2luZ2xlIGdlbmUgYWNjb3VudGVkIGZvciBhbGwgY291bnRzIGluIGEgcGFydGljdWxhciBjZWxsPwpUaGlzIGNlbGwgd291bGQgbm90IGhhdmUgaGVscGZ1bCBkYXRhIGZvciB1cywgc28gd2Ugc2hvdWxkIGxvb2sgdG8gcmVtb3ZlIGFueSBjZWxscyB3ZSBzdXNwZWN0IG1pZ2h0IG5vdCBoYXZlIGEgdXNlZnVsIGFtb3VudCBvZiBpdHMgdHJhbnNjcmlwdG9tZSBtZWFzdXJlZC4KCkJ1dCBiZWZvcmUgd2UgY2FuIGRldGVybWluZSBob3cgbWFueSBnZW5lcyB3ZSBjb25zaWRlciBhIHBhcnRpY3VsYXIgY2VsbCB0byBiZSBleHByZXNzaW5nIHdlIG5lZWQgdG8gZGV0ZXJtaW5lIGEgbnVtZXJpYyBjdXRvZmYgZm9yIHdoYXQgd2UgY29uc2lkZXIgdG8gYmUgYSBkZXRlY3RlZCBnZW5lLgpIb3cgbWFueSBjb3VudHMgbXVzdCB0aGVyZSBiZSBmb3IgeW91IHRvIGNvbnNpZGVyIGEgZ2VuZSBleHByZXNzZWQ/CkhlcmUgbGV0J3MgZ28gZm9yIGEgc2ltcGxlIGRldGVjdGlvbiBjdXRvZmYgb2YgPiAwLgoKYGBge3IgZGV0ZWN0aW9uX21hdHJpeCwgbGl2ZT1UUlVFfQojIG1ha2UgYSBkZXRlY3Rpb25fbWF0IG1hdHJpeCB0aGF0IGlzIFRSVUUgd2hlbiBhIGdlbmUgaXMgZXhwcmVzc2VkIGluIGEgc2FtcGxlCmRldGVjdGlvbl9tYXQgPC0gc2NfY291bnRzID4gMApgYGAKCk5vdyB0aGF0IHdlIGhhdmUgdHVybmVkIG91ciBkYXRhIGludG8gYSBtYXRyaXggb2YgYFRSVUUvRkFMU0VgIGZvciBkZXRlY3Rpb24sIHdlIGNhbiBzdW0gdGhpcyBkYXRhIGJ5IGNvbHVtbiB0byBlZmZlY3RpdmVseSBnZXQgYSB2ZWN0b3Igb2YgaG93IG1hbnkgZ2VuZXMgd2VyZSBtZWFzdXJlZCBpbiBlYWNoIGNlbGwuCgpgYGB7ciBnZW5lc19leHByZXNzZWQsIGxpdmUgPSBUUlVFfQojIE1ha2UgYSB2ZWN0b3IgdGhhdCBjb250YWlucyB0aGUgbnVtYmVyIG9mIGdlbmVzIGV4cHJlc3NlZCBieSBhIHBhcnRpY3VsYXIgY2VsbApudW1fZ2VuZXNfZXhwIDwtIGNvbFN1bXMoZGV0ZWN0aW9uX21hdCkKYGBgCgpMZXQncyBwbG90IHRoaXMgdXNpbmcgdGhlIHNhbWUgc3R5bGUgYW5kIHR5cGUgb2YgZ3JhcGggYXMgYWJvdmUuCgpgYGB7ciBnZW5lc19leHByZXNzZWRfcGxvdH0KZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IG51bV9nZW5lc19leHApKSArCiAgZ2VvbV9kZW5zaXR5KGZpbGwgPSAibGlnaHRibHVlIikgKwogIGxhYnMoeCA9ICJOdW1iZXIgb2YgZ2VuZXMgZXhwcmVzc2VkIikgKwogIHRoZW1lX2NsYXNzaWMoKQpgYGAKClRoaXMgcGxvdCBoZWxwcyB1cyB2aXN1YWxpemUgdGhlIGRpc3RyaWJ1dGlvbiBvZiBnZW5lcyBwZXIgY2VsbCBhbmQgY2FuIGhlbHAgaW5mb3JtIGhvdyB3ZSBjaG9vc2UgdGhlIGN1dG9mZi4KSXQncyBpbXBvcnRhbnQgdG8gcmVtZW1iZXIgdGhhdCBkaWZmZXJlbnQgY2VsbCB0eXBlcyBjYW4gaGF2ZSBxdWl0ZSBkaWZmZXJlbnQgcGF0dGVybnMgd2l0aCByZWdhcmRzIHRvIG51bWJlciBvZiBnZW5lcyBleHByZXNzZWQuCklmIHdlIHdlcmUgdG8gdXNlIHN0cmljdCBjdXRvZmZzIHRvIHNlbGVjdCB3aGljaCBjZWxscyBhcmUgInZhbGlkIiwgdGhlcmUgaXMgdGhlIHBvc3NpYmlsaXR5IHRoYXQgd2UgY291bGQgYmlhcyBvdXIgcmVzdWx0cywgc28gdGhpcyBpcyBzb21ldGhpbmcgd2Ugd2FudCB0byBiZSBjYXJlZnVsIGFib3V0LgoKTGV0J3Mgc2VlIHdoYXQgaGFwcGVucyBpZiB3ZSBvbmx5IGtlZXAgY2VsbHMgd2l0aCA+IDUwMCBleHByZXNzZWQgZ2VuZXMuCkp1c3QgbGlrZSB3aGVuIHdlIGxvb2tlZCBhdCB0b3RhbCBjb3VudHMsIHdlIGNhbiBhZGQgaW4gYSB2ZXJ0aWNhbCBsaW5lIHRvIHRoZSBwcmV2aW91cyBwbG90IHdoZXJlIHRoZSBwb3NzaWJsZSBjdXRvZmYgd291bGQgYmUuCgpgYGB7ciBnZW5lc19leHByZXNzZWRfY3V0b2ZmLCBsaXZlID0gVFJVRX0KZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IG51bV9nZW5lc19leHApKSArCiAgZ2VvbV9kZW5zaXR5KGZpbGwgPSAibGlnaHRibHVlIikgKwogIGxhYnMoeCA9ICJOdW1iZXIgb2YgZ2VuZXMgZXhwcmVzc2VkIikgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNTAwLCBjb2xvciA9ICJyZWQiKQpgYGAKSG93IG1hbnkgY2VsbHMgd291bGQgYmUgcmVtb3ZlZCB3aXRoIHRoaXMgY3V0b2ZmPwoKYGBge3IgY291bnRfZ2VuZV9jdXRvZmZzLCBsaXZlID0gVFJVRX0KIyBDYWxjdWxhdGUgdGhlIG51bWJlciBvZiBjZWxscyB0aGF0IHdvdWxkIGJlIHJlbW92ZWQgd2l0aCBhIGdpdmVuIGN1dG9mZgpnZW5lX2N1dG9mZiA8LSA1MDAKc3VtKG51bV9nZW5lc19leHAgPD0gZ2VuZV9jdXRvZmYpCmBgYAoKIyMjIE1pdG9jaG9uZHJpYWwgZ2VuZSBleHByZXNzaW9uCgpJZiBhIGNlbGwgaXMgZGVhZCBvciBkeWluZywgaXRzIG1STkEgd2lsbCB0ZW5kIHRvIGxlYWsgb3V0IG9mIHRoZSBjZWxsLCBsZWF2aW5nIGFuIG92ZXJhYnVuZGFuY2Ugb2YgbWl0b2Nob25kcmlhbCBSTkEsIHdoaWNoIGlzIG1vcmUgbGlrZWx5IHRvIHN0YXkgd2l0aGluIHRoZSBtaXRvY2hvbmRyaWEgbG9uZ2VyLgpUbyBsb29rIGZvciB0aGlzLCB3ZSB3b3VsZCBsaWtlIHRvIGNhbGN1bGF0ZSB0aGUgZnJhY3Rpb24gb2YgbWl0b2Nob25kcmlhbCBleHByZXNzaW9uIGZvciBlYWNoIGNlbGwgYXMgd2VsbC4KRmlyc3QsIHdlIHdpbGwgbmVlZCBhIGxpc3Qgb2YgdGhlIG1pdG9jaG9uZHJpYWwgZ2VuZXMsIHdoaWNoIHdlIGhhdmUgcHJlcGFyZWQgaW4gYSB0c3YgZmlsZSBgbW1fbWl0b2Nob25kcmlhbF9nZW5lcy50c3ZgIHRoYXQgd2Ugd2lsbCBub3cgcmVhZCBpbiwgYW5kIGZpbHRlciB0byBqdXN0IHRoZSBnZW5lcyB0aGF0IGFyZSBmb3VuZCBpbiB0aGUgZGF0YSBzZXQuCgoKYGBge3IgcmVhZF9taXRvfQojIHJlYWQgYG1tX21pdG9jaG9uZHJpYWxfZ2VuZXMudHN2YCBmcm9tIHJlZl9kaXIgYW5kCiMgY3JlYXRlIGZyb20gaXQgYSBzaW5nbGUgdmVjdG9yIGNvbnRhaW5pbmcgb25seSB0aGUgZ2VuZSBpZHMKbWl0b19nZW5lcyA8LSByZWFkcjo6cmVhZF90c3YobWl0b19maWxlKSB8PgogICMgZmlsdGVyIHRvIG9ubHkgZ2VuZSBpbiB0aGUgc2NlIG9iamVjdAogIGRwbHlyOjpmaWx0ZXIoZ2VuZV9pZCAlaW4lIHJvd25hbWVzKGJsYWRkZXJfc2NlKSkgfD4KICAjIHB1bGwgdGFrZXMgdGhpcyBjb2x1bW4gb3V0IG9mIHRoZSBkYXRhIGZyYW1lIGFzIGEgc3RhbmQtYWxvbmUgdmVjdG9yCiAgZHBseXI6OnB1bGwoZ2VuZV9pZCkKYGBgCgpOb3cgd2UgY2FuIHVzZSB0aGUgZ2VuZXMgZnJvbSB0aGF0IGxpc3QgdG8gc2VsZWN0IG9ubHkgdGhlIHJvd3Mgb2YgdGhlIGNvdW50IG1hdHJpeCB0aGF0IGNvcnJlc3BvbmQgdG8gdGhlIG1pdG9jaG9uZHJpYWwgZ2VuZXMgYW5kIHN1bSB0aGVpciBleHByZXNzaW9uIGZvciBlYWNoIHNhbXBsZS4KCmBgYHtyIG1pdG9fZmlsdGVyLCBsaXZlID0gVFJVRX0KIyBjcmVhdGUgYSBtaXRvX3Jvd3MgdmVjdG9yIHRoYXQgaXMgVFJVRSBmb3IgbWl0b2Nob25kcmlhbCBnZW5lcyBpbiBvdXIgZGF0YXNldAptaXRvX3Jvd3MgPC0gcm93bmFtZXMoc2NfY291bnRzKSAlaW4lIG1pdG9fZ2VuZXMKCiMgc3VtIHRoZSBjb3VudHMgZnJvbSBqdXN0IHRob3NlIGdlbmVzIGZvciBhbGwgc2FtcGxlcwptaXRvX2NvdW50cyA8LSBjb2xTdW1zKHNjX2NvdW50c1ttaXRvX3Jvd3MsIF0pCgojIGNhbGN1bGF0ZSBtaXRvX2ZyYWN0aW9uIGZvciBhbGwgc2FtcGxlcwptaXRvX2ZyYWN0aW9uIDwtIG1pdG9fY291bnRzL3RvdGFsX2NvdW50cwpgYGAKCkxldHMgbWFrZSBhIHBsb3Qgb2YgdGhpcyBkaXN0cmlidXRpb24gYXMgd2VsbCEKCmBgYHtyIHBsb3RfbWl0bywgbGl2ZSA9IFRSVUV9CmdncGxvdChtYXBwaW5nID0gYWVzKHggPSBtaXRvX2ZyYWN0aW9uKSkgKwogIGdlb21fZGVuc2l0eShmaWxsID0gImxpZ2h0Ymx1ZSIpICsKICBsYWJzKHggPSAiTWl0Y2hvbmRyaWFsIGZyYWN0aW9uIikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAuMiwgY29sb3IgPSAicmVkIikgKwogIHRoZW1lX2NsYXNzaWMoKQpgYGAKSGVyZSwgd2Ugd2FudCB0byBrZWVwIGNlbGxzIHdpdGggYSBsb3cgZnJhY3Rpb24gb2YgcmVhZHMgY29ycmVzcG9uZGluZyB0byBtaXRvY2hvbmRyaWFsIGdlbmVzIGFuZCByZW1vdmUgYW55IGNlbGxzIHdpdGggYSBoaWdoIG1pdG9jaG9uZHJpYWwgZnJhY3Rpb24uCkFnYWluLCBpdCdzIGltcG9ydGFudCB0byB0YWtlIHRoaXMgc3RlcCBldmVuIGlmIHlvdSBzdGFydGVkIHdpdGggZmlsdGVyZWQgZGF0YSwgc2luY2UgbWFwcGluZyBzb2Z0d2FyZSBsaWtlIGBzYWxtb24gYWxldmluYCBhbmQgQ2VsbCBSYW5nZXIgZG8gbm90IHVzdWFsbHkgY29uc2lkZXIgbWl0b2Nob25kcmlhbCByZWFkIHBlcmNlbnRhZ2VzIHdoZW4gZmlsdGVyaW5nLgoKIyMjIENvbWJpbmluZyBzYW1wbGUgUUMgbWVhc3VyZXMKCkxldHMgcHV0IGFsbCBvZiB0aGUgUUMgbWVhc3VyZXMgd2UgaGF2ZSBjYWxjdWxhdGVkIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZSwgc28gd2UgY2FuIGxvb2sgYXQgaG93IHRoZXkgbWlnaHQgcmVsYXRlIHRvIG9uZSBhbm90aGVyLgoKYGBge3IgcWNfZGF0YWZyYW1lLCBsaXZlID0gVFJVRX0KIyBtYWtlIGEgZGF0YSBmcmFtZSB3aXRoIG51bWJlciBvZiBnZW5lcyBleHByZXNzZWQsIHRvdGFsIGNvdW50cywgYW5kIG1pdG8gZnJhY3Rpb24KcWNfZGYgPC0gZGF0YS5mcmFtZShiYXJjb2RlID0gbmFtZXMobnVtX2dlbmVzX2V4cCksCiAgICAgICAgICAgICAgICAgICAgZ2VuZXNfZXhwID0gbnVtX2dlbmVzX2V4cCwKICAgICAgICAgICAgICAgICAgICB0b3RhbF9jb3VudHMgPSB0b3RhbF9jb3VudHMsCiAgICAgICAgICAgICAgICAgICAgbWl0b19mcmFjdGlvbiA9IG1pdG9fZnJhY3Rpb24pCgpgYGAKCk5vdyB3ZSBjYW4gcGxvdCB0aGVzZSBtZWFzdXJlcyBhbGwgdG9nZXRoZXIsIGFsb25nIHdpdGggc29tZSBwb3NzaWJsZSBjdXRvZmZzLgoKYGBge3IgcWNfc2NhdHRlcnBsb3R9CmdncGxvdChxY19kZiwgYWVzICh4ID0gdG90YWxfY291bnRzLAogICAgICAgICAgICAgICAgICAgeSA9IGdlbmVzX2V4cCwKICAgICAgICAgICAgICAgICAgIGNvbG9yID0gbWl0b19mcmFjdGlvbikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDEwMDAsIGNvbG9yID0gInJlZCIpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSA1MDAsIGNvbG9yID0gInJlZCIpICsKICBsYWJzKHggPSAiVG90YWwgQ291bnQiLAogICAgICAgeSA9ICJOdW1iZXIgb2YgR2VuZXMgRXhwcmVzc2VkIiwKICAgICAgIGNvbG9yID0gIk1pdG9jaG9uZHJpYWxcbkZyYWN0aW9uIikgKwogIHRoZW1lX2J3KCkKYGBgCgpJZiB3ZSB3YW50IHRvIGZpbHRlciBvdXIgZGF0YSBiYXNlZCBvbiB0aGVzZSBtZWFzdXJlcyBhbmQgY3V0b2ZmcyB3ZSBsaWtlLCB3ZSBjYW4gZG8gdGhpcyB3aXRoIGBkcGx5cjo6ZmlsdGVyKClgIGFuZCB0aGVuIHNlbGVjdCB0aGUgcmVzdWx0aW5nIGNvbHVtbnMgZnJvbSB0aGUgbWF0cml4LgoKYGBge3IgcWNfZmlsdGVyLCBsaXZlID0gVFJVRX0KIyBjcmVhdGUgYSBmaWx0ZXJlZF9zYW1wbGVzIGRhdGEgZnJhbWUgZnJvbSBxY19kZgpmaWx0ZXJlZF9zYW1wbGVzIDwtIHFjX2RmIHw+CiAgZHBseXI6OmZpbHRlcih0b3RhbF9jb3VudHMgPiAxMDAwLAogICAgICAgICAgICAgICAgZ2VuZXNfZXhwID4gNTAwLAogICAgICAgICAgICAgICAgbWl0b19mcmFjdGlvbiA8IDAuMikKIyBzZWxlY3Qgb25seSBwYXNzaW5nIHNhbXBsZXMgZm9yIGJsYWRkZXJfc2NlX2ZpbHRlcmVkCnNjX2NvdW50c19maWx0ZXJlZCA8LSBzY19jb3VudHNbLCBmaWx0ZXJlZF9zYW1wbGVzJGJhcmNvZGVdCmBgYAoKIyMgRmlsdGVyaW5nIHRoZSBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIGRpcmVjdGx5CgojIyMgQ2FsY3VsYXRpbmcgY2VsbCBRQyBzdGF0cyB3aXRoIGBzY2F0ZXJgCgpUaGUgbWV0aG9kcyBhYm92ZSB3ZXJlIG5pY2UgZm9yIGRlbW9uc3RyYXRpbmcgdGhlIGtpbmRzIG9mIGZpbHRlcmluZyB3ZSBtaWdodCBkbywgYnV0IGFsbCB0aGUgc3RlcHMgd291bGQgY2VydGFpbmx5IGJlIHJlcGV0aXRpdmUgaWYgd2UgaGFkIHRvIGRvIHRoZW0gZm9yIGVhY2ggc2FtcGxlLgpUaGFua2Z1bGx5LCB0aGVyZSBhcmUgc29tZSBuaWNlIG1ldGhvZHMgdGhhdCBoYXZlIGJlZW4gZGV2ZWxvcGVkIGluIHBhY2thZ2VzIGxpa2UgYHNjYXRlcmAgdG8gcGVyZm9ybSB0aGVtIGFsbCBhdCBvbmNlIGFuZCBhZGQgdGhlIHJlc3VsdHMgdG8gdGhlIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0LgpUaGUgYWR2YW50YWdlcyBvZiB1c2luZyBmdW5jdGlvbnMgbGlrZSB0aGlzIGFyZSB0aGF0IHdlIGNhbiBrZWVwIGFsbCBvZiB0aGUgbWV0YWRhdGEgdG9nZXRoZXIsIGZpbHRlciBkaXJlY3RseSBvbiB0aGUgb2JqZWN0IG9mIGludGVyZXN0LCBhdm9pZCBhIGxvdCBvZiByZXBldGl0aW9uLCBhbmQgaW4gZG9pbmcgc28gYXZvaWQgbWFueSBwb3RlbnRpYWwgZXJyb3JzLgoKV2Ugd2lsbCBzdGFydCB3aXRoIHRoZSBmdW5jdGlvbiBgYWRkUGVyQ2VsbFFDKClgLCB3aGljaCB0YWtlcyBhIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgYW5kIGEgbGlzdCBvZiBnZW5lIHNldHMgdGhhdCB0aGF0IHdlIG1pZ2h0IHdhbnQgdG8gY2FsY3VsYXRlIHN1YnNldCBpbmZvcm1hdGlvbiBmb3IuCkluIG91ciBjYXNlLCB3ZSB3aWxsIGp1c3QgbG9vayBhdCBtaXRvY2hvbmRyaWFsIGdlbmVzIGFnYWluLgoKYGBge3J9CmJsYWRkZXJfc2NlIDwtIHNjYXRlcjo6YWRkUGVyQ2VsbFFDKAogIGJsYWRkZXJfc2NlLAogICMgYSBsaXN0IG9mIG5hbWVkIGdlbmUgc3Vic2V0cyB0aGF0IHdlIHdhbnQgc3RhdHMgZm9yCiAgIyBoZXJlIHdlIGFyZSB1c2luZyBtaXRvY2hvbmRyaWFsIGdlbmVzCiAgc3Vic2V0cyA9IGxpc3QobWl0byA9IG1pdG9fZ2VuZXMpCiAgKQpgYGAKClRoZSByZXN1bHRzIG9mIHRoZXNlIGNhbGN1bGF0aW9ucyBhcmUgbm93IHN0b3JlZCBhcyBhIGRhdGEgZnJhbWUgaW4gdGhlIGBjb2xEYXRhYCBzbG90IG9mIHRoZSBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIG9iamVjdCwgd2hpY2ggd2UgY2FuIHB1bGwgb3V0IHdpdGggdGhlIGBjb2xEYXRhKClgIGZ1bmN0aW9uLgooVW5mb3J0dW5hdGVseSwgaXQgaXMgbm90IHF1aXRlIGEgcmVndWxhciBkYXRhIGZyYW1lLCBidXQgd2UgY2FuIGVhc2lseSBjb252ZXJ0IGl0IHRvIG9uZS4pCkV2ZW4gbmljZXIsIHdlIGNhbiBhY2Nlc3MgdGhlIFFDIGRhdGEgaW4gdGhvc2UgY29sdW1ucyBkaXJlY3RseSB3aXRoIGp1c3QgdGhlIGZhbWlsaWFyIGAkYCBzeW50YXghCgpUaGUgY2FsY3VsYXRlZCBzdGF0aXN0aWNzIGluY2x1ZGUgYHN1bWAsIHRoZSB0b3RhbCBVTUkgY291bnQgZm9yIHRoZSBjZWxsLCBgZGV0ZWN0ZWRgLCB0aGUgbnVtYmVyIG9mIGdlbmVzIGRldGVjdGVkLCBhbmQgYSBmZXcgZGlmZmVyZW50IHN0YXRpc3RpY3MgZm9yIGVhY2ggc3Vic2V0IHRoYXQgd2UgZ2F2ZSwgaW5jbHVkaW5nIHRoZSBwZXJjZW50IChub3QgZnJhY3Rpb24hKSBvZiBhbGwgVU1JcyBmcm9tIHRoZSBzdWJzZXQuClNpbmNlIHRoZSBzdWJzZXQgd2UgdXNlZCB3YXMgbmFtZWQgYG1pdG9gLCB0aGlzIGNvbHVtbiBpcyBjYWxsZWQgYHN1YnNldHNfbWl0b19wZXJjZW50YC4KClVzaW5nIHRoZXNlLCB3ZSBjYW4gcmVjcmVhdGUgdGhlIHBsb3QgZnJvbSBiZWZvcmU6CgpgYGB7ciByZXBsb3R9CiMgZXh0cmFjdCB0aGUgY29sdW1uIGRhdGEgYW5kIGNvbnZlcnQgdG8gYSBkYXRhIGZyYW1lCmJsYWRkZXJfcWMgPC0gZGF0YS5mcmFtZShjb2xEYXRhKGJsYWRkZXJfc2NlKSkKCiMgcGxvdCB3aXRoIHRoZSBxYyBkYXRhIGZyYW1lCmdncGxvdChibGFkZGVyX3FjLCBhZXMgKHggPSBzdW0sCiAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBkZXRlY3RlZCwKICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IHN1YnNldHNfbWl0b19wZXJjZW50KSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArCiAgbGFicyh4ID0gIlRvdGFsIENvdW50IiwKICAgICAgIHkgPSAiTnVtYmVyIG9mIEdlbmVzIEV4cHJlc3NlZCIsCiAgICAgICBjb2xvciA9ICJNaXRvY2hvbmRyaWFsXG5GcmFjdGlvbiIpICsKICB0aGVtZV9idygpCmBgYAoKIyMjIEFwcGx5aW5nIGEgZmlsdGVyIHRvIGEgYFNpbmdsZUNlbGxFeHBlcmltZW50YAoKRmlsdGVyaW5nIHRoZSBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIG9iamVjdCBpcyBkb25lIGFzIGlmIGl0IHdlcmUganVzdCB0aGUgY291bnRzIG1hdHJpeCwgd2l0aCBicmFja2V0cyBhbmQgaW5kZXhlcy4KV2hpbGUgdGhpcyB3aWxsIGxvb2sgbXVjaCBsaWtlIHdoYXQgd2UgZGlkIGJlZm9yZSwgaXQgaXMgYmV0dGVyLCBiZWNhdXNlIGl0IHdpbGwgYWxzbyBrZWVwIHRoZSBmaWx0ZXJlZCBRQyBzdGF0cyBhbG9uZ3NpZGUsIGluIGNhc2Ugd2Ugd2FudGVkIHRvIHJldmlzaXQgdGhlbSBsYXRlci4KT3RoZXJ3aXNlLCB3ZSB3b3VsZCBoYXZlIHRvIGZpbHRlciBvdXIgUUMgcmVzdWx0cyBzZXBhcmF0ZWx5LCB3aGljaCBpcyBhbiBlYXN5IHBsYWNlIGZvciBlcnJvcnMgdG8gY3JlZXAgaW4uCgpgYGB7ciBmaWx0ZXJfc2NlfQojIGNyZWF0ZSBhIGJvb2xlYW4gdmVjdG9yIG9mIFFDIGZpbHRlcnMKY2VsbHNfdG9fa2VlcCA8LSBibGFkZGVyX3NjZSRzdW0gPiAxMDAwICYKICBibGFkZGVyX3NjZSRkZXRlY3RlZCA+IDUwMCAmCiAgYmxhZGRlcl9zY2Ukc3Vic2V0c19taXRvX3BlcmNlbnQgPCAyMAoKIyBmaWx0ZXIgdGhlIHNjZSBvYmplY3QgKGNlbGxzIGFyZSBjb2x1bW5zKQpibGFkZGVyX3NjZV9maWx0ZXJlZCA8LSBibGFkZGVyX3NjZVssIGNlbGxzX3RvX2tlZXBdCmBgYAoKSnVzdCB0byBjaGVjaywgd2Ugc2hvdWxkIGhhdmUgdGhlIHNhbWUgbnVtYmVyIG9mIGNlbGxzIGluIGBibGFkZGVyX3NjZV9maWx0ZXJlZGAgYXMgb3VyIHByZXZpb3VzIGBzY19jb3VudHNfZmlsdGVyZWRgLgoKYGBge3IgY2hlY2tfY2VsbF9jb3VudCwgbGl2ZSA9IFRSVUV9Cm5jb2woc2NfY291bnRzX2ZpbHRlcmVkKSA9PSBuY29sKGJsYWRkZXJfc2NlX2ZpbHRlcmVkKQpgYGAKCiMjIE51bWJlciBvZiBjZWxscyB0aGF0IGV4cHJlc3MgYSBnZW5lIGFzIGEgcXVhbGl0eSBtZWFzdXJlCgpOb3cgd2UgaGF2ZSBhbiBpZGVhIG9mIHdoYXQgY2VsbHMgd2UgcHJvYmFibHkgd2FudCB0byBnZXQgcmlkIG9mLgpCdXQgd2hhdCBpZiBvdXIgZGF0YSBjb250YWlucyBnZW5lcyB0aGF0IHdlIGNhbid0IHJlbGlhYmx5IG1lYXN1cmUgaW4gdGhlc2UgY2VsbHM/CgpXZSBjb3VsZCB1c2Ugb3VyIGVhcmxpZXIgYGRldGVjdGlvbl9tYXRgIHRvIGFkZCB1cCBob3cgbWFueSBjZWxscyBleHByZXNzIGVhY2ggZ2VuZSwgYnV0IHdlIHdpbGwgc2tpcCBzdHJhaWdodCB0byB0aGUgYHNjYXRlcmAgZnVuY3Rpb24gdGhpcyB0aW1lLCB3aGljaCBpcyBjYWxsZWQgYGFkZFBlckZlYXR1cmVRQygpYC4KVGhpcyB3aWxsIGFkZCBRQyBzdGF0aXN0aWNzIHRvIHRoZSBgcm93RGF0YWAgZm9yIGVhY2ggZ2VuZSAoYWxvbmdzaWRlIHRoZSBhbm5vdGF0aW9uIGRhdGEgd2UgYWxyZWFkeSBoYWQgdGhlcmUpClRoZSBjb2x1bW5zIGl0IGFkZHMgYXJlIHRoZSBhdmVyYWdlIGV4cHJlc3Npb24gbGV2ZWwgb2YgZWFjaCBnZW5lIChgbWVhbmApIGFuZCB0aGUgcGVyY2VudGFnZSBvZiBjZWxscyBpbiB3aGljaCBpdCB3YXMgZGV0ZWN0ZWQgKGBkZXRlY3RlZGApLgoKYGBge3Igc2FtcGxlX2V4cCwgbGl2ZSA9IFRSVUV9CmJsYWRkZXJfc2NlX2ZpbHRlcmVkIDwtIHNjYXRlcjo6YWRkUGVyRmVhdHVyZVFDKGJsYWRkZXJfc2NlX2ZpbHRlcmVkKQpgYGAKCkxldCdzIG1ha2UgYW5vdGhlciBkZW5zaXR5IHBsb3Qgd2l0aCB0aGUgcGVyY2VudGFnZSBvZiBzYW1wbGVzIHRoYXQgZXhwcmVzcyBlYWNoIGdlbmU6CgpgYGB7ciBzYW1wbGVfZXhwX3Bsb3R9CiMgZXh0cmFjdCB0aGUgZ2VuZSBpbmZvcm1hdGlvbiB3aXRoCmdlbmVfaW5mbyA8LSBkYXRhLmZyYW1lKHJvd0RhdGEoYmxhZGRlcl9zY2VfZmlsdGVyZWQpKQoKIyBQbG90IHRoZSBkZXRlY3RlZCBwZXJjZW50YWdlCmdncGxvdChnZW5lX2luZm8sIGFlcyh4ID0gZGV0ZWN0ZWQpICkrCiAgZ2VvbV9kZW5zaXR5KGZpbGwgPSAibGlnaHRibHVlIikgKwogIGxhYnMoeCA9ICJQZXJjZW50IG9mIENlbGxzIEV4cHJlc3NpbmcgRWFjaCBHZW5lIikgKwogIHRoZW1lX2NsYXNzaWMoKQpgYGAKCkhvdyBtYW55IGdlbmVzIHdpbGwgYmUgZXhjbHVkZWQgaWYgd2UgZHJhdyBvdXIgY3V0b2ZmIGF0IDUlIG9mIGNlbGxzPwoKYGBge3IgZmlsdGVyX2VmZmVjdCwgbGl2ZSA9IFRSVUV9CnN1bShnZW5lX2luZm8kZGV0ZWN0ZWQgPCA1KQpgYGAKClRoYXQncyBhIGxvdCEgSG93IGRvIHdlIGZlZWwgYWJvdXQgdGhhdD8KCmBgYHtyIGZpbHRlcl9nZW5lcywgbGl2ZSA9IFRSVUV9CmN1dG9mZiA8LSAyCiMgZmlsdGVyIGJsYWRkZXJfc2NlX2ZpbHRlcmVkIHRvIG9ubHkgZ2VuZXMgYWJvdmUgYSBjdXRvZmYgdmFsdWUKYmxhZGRlcl9zY2VfZmlsdGVyZWQgPC0gYmxhZGRlcl9zY2VfZmlsdGVyZWRbZ2VuZV9pbmZvJGRldGVjdGVkID49IGN1dG9mZiwgXQpgYGAKCkhvdyBiaWcgaXMgdGhlIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0IG5vdz8KCmBgYHtyIGZpbHRlcmVkX3NpemUsIGxpdmUgPSBUUlVFfQpkaW0oYmxhZGRlcl9zY2VfZmlsdGVyZWQpCmBgYAoKIyMgU2F2ZSB0aGUgZmlsdGVyZWQgZGF0YQoKV2Ugd2lsbCBzYXZlIHRoZSBmaWx0ZXJlZCBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIG9iamVjdCBhcyBhIGAucmRzYCBmaWxlIGZvciBsYXRlciB1c2UuCgpgYGB7ciBzYXZlX3Jkc30KIyBTYXZlIG9iamVjdCB0byB0aGUgZmlsZSBmaWx0ZXJlZF9zY2VfZmlsZSwgd2hpY2gKIyB3ZSBkZWZpbmVkIGF0IHRoZSB0b3Agb2YgdGhpcyBub3RlYm9vawpyZWFkcjo6d3JpdGVfcmRzKGJsYWRkZXJfc2NlX2ZpbHRlcmVkLCBmaWxlID0gZmlsdGVyZWRfc2NlX2ZpbGUpCmBgYAoKCiMjIyBQcmludCBzZXNzaW9uIGluZm8KCmBgYHtyIHNlc3Npb25pbmZvfQpzZXNzaW9uSW5mbygpCmBgYAo=