Last updated: 2025-05-19

Checks: 7 0

Knit directory: DigitalResearchSkillsNetwork/

This reproducible R Markdown analysis was created with workflowr (version 1.7.1). The Checks tab describes the reproducibility checks that were applied when the results were created. The Past versions tab lists the development history.


Great! Since the R Markdown file has been committed to the Git repository, you know the exact version of the code that produced these results.

Great job! The global environment was empty. Objects defined in the global environment can affect the analysis in your R Markdown file in unknown ways. For reproduciblity it’s best to always run the code in an empty environment.

The command set.seed(1337) was run prior to running the code in the R Markdown file. Setting a seed ensures that any results that rely on randomness, e.g. subsampling or permutations, are reproducible.

Great job! Recording the operating system, R version, and package versions is critical for reproducibility.

Nice! There were no cached chunks for this analysis, so you can be confident that you successfully produced the results during this run.

Great job! Using relative paths to the files within your workflowr project makes it easier to run your code on other machines.

Great! You are using Git for version control. Tracking code development and connecting the code version to the results is critical for reproducibility.

The results in this page were generated with repository version 61125d7. See the Past versions tab to see a history of the changes made to the R Markdown and HTML files.

Note that you need to be careful to ensure that all relevant files for the analysis have been committed to Git prior to generating the results (you can use wflow_publish or wflow_git_commit). workflowr only checks the R Markdown file, but you know if there are other scripts or data files that it depends on. Below is the status of the Git repository when the results were generated:


Ignored files:
    Ignored:    .DS_Store
    Ignored:    .Rhistory
    Ignored:    analysis/.DS_Store
    Ignored:    analysis/.RData
    Ignored:    analysis/.Rhistory
    Ignored:    analysis/adit/.DS_Store
    Ignored:    plots/
    Ignored:    raw/

Untracked files:
    Untracked:  analysis/adit/0_content management.csv
    Untracked:  analysis/x202504_Workshop.Rmd

Unstaged changes:
    Deleted:    analysis/202504_Workshop.Rmd
    Modified:   analysis/_site.yml
    Modified:   workflow.R

Note that any generated files, e.g. HTML, png, CSS, etc., are not included in this status report because it is ok for generated content to have uncommitted changes.


These are the previous versions of the repository in which changes were made to the R Markdown (analysis/202505_Workshop.Rmd) and HTML (docs/202505_Workshop.html) files. If you’ve configured a remote Git repository (see ?wflow_git_remote), click on the hyperlinks in the table below to view the files as they were in that past version.

File Version Author Date Message
Rmd 61125d7 DrThomasOneil 2025-05-19 wflow_publish(c("analysis/0_bugs.Rmd", "analysis/202505_Workshop.Rmd"))
html e794405 DrThomasOneil 2025-05-15 Build site.
html fb553e5 DrThomasOneil 2025-05-14 Build site.
Rmd a5f07d7 DrThomasOneil 2025-05-14 wflow_publish(files)
html c0fa3bc DrThomasOneil 2025-05-12 Build site.
Rmd f349caf DrThomasOneil 2025-05-12 wflow_publish("analysis/202505_Workshop.Rmd")
html cff7923 DrThomasOneil 2025-05-07 Build site.
Rmd ff95f8f DrThomasOneil 2025-05-07 wflow_publish(files)
html 16a9613 DrThomasOneil 2025-04-30 Build site.
html 862d6b6 DrThomasOneil 2025-04-29 Build site.
Rmd b68287f DrThomasOneil 2025-04-29 wflow_publish(files)

Please try to follow the instructions below to set up your analysis prior to attending the workshop.

Be aware!

It is difficult to debug over zoom.
If you want the best feedback and experience, make sure you attend the workshop in person!

You can download the script on the top right or from this link.

Introduction

Setup

Create Folders

├── YourFolder
│     └── raw (where we can deposit raw downloaded data)
│     └── data (where we can process data and store it)
│     └── plots (where we can store plots)
if(!dir.exists("raw")){dir.create("raw")}
if(!dir.exists("data")){dir.create("data")}
if(!dir.exists("plots")){dir.create("plots")}

Install Packages

install.packages("Seurat")
install.packages("tidyverse")
install.packages("cowplot")
install.packages("hdf5r")

We have a function that will let you check the setup

source("https://github.com/DrThomasOneil/Digital-Research-Skills-Network/raw/refs/heads/main/docs/adit/checksetup.R")

checkSetup(
  cran_packages = c("Seurat", "tidyverse", "cowplot","hdf5r"),
  bioc_packages = c()
  )

--------------------------------------

***Installing General Packages***

Seurat is loaded!
tidyverse is loaded!
cowplot is loaded!
hdf5r is loaded!

--------------------------------------
 
 All packages are loaded!

 Happy Coding! :)

set.seed(1337)

Download data

We will download the data directly from the GEO. We’ll use this dataset, which was published in 2024. Additionally, this group have published all of their methods and scripts for analysis.

# Go to the GEO page and right click on the http link under download. It should look like this

data_file <- "https://www.ncbi.nlm.nih.gov/geo/download/?acc=GSE290350&format=file"

# download the data
if(!file.exists("raw/GSE290350_RAW.tar")){download.file(url=data_file, destfile = "./raw/GSE290350_RAW.tar", method='curl')}

# repeat for metadata
if(!file.exists("raw/GSE290350_metadata.csv.gz")){download.file(url='https://www.ncbi.nlm.nih.gov/geo/download/?acc=GSE290350&format=file&file=GSE290350%5Fmetadata%2Ecsv%2Egz', destfile = "raw/GSE290350_metadata.csv.gz", method='curl')}

# and the supposed processed Seurat data
if(!file.exists("raw/GSE290350_seuratObj_spatial_dist.RDS")){download.file(url="https://www.ncbi.nlm.nih.gov/geo/download/?acc=GSE290350&format=file&file=GSE290350%5FseuratObj%5Fspatial%5Fdist%2ERDS", destfile = "raw/GSE290350_seuratObj_spatial_dist.RDS", method='curl')}

Unzip and untar the raw data

untar("raw/GSE290350_RAW.tar", exdir = "./raw/GSE290350_RAW")
samples <- list.files("raw/GSE290350_RAW", pattern = "\\.tar\\.gz$", full.names = TRUE)

for (i in seq_along(samples)) {
  tar_path <- sub("\\.gz$", "", samples[i])

  # Unzip the .tar.gz file
  R.utils::gunzip(samples[i], destname = tar_path, overwrite = TRUE)

  output_dir <- file.path("raw/GSE290350_RAW", sub("\\.tar$", "", basename(tar_path)))
  dir.create(output_dir, showWarnings = FALSE)

  # Extract tar to output directory
  untar(tar_path, exdir = output_dir)
}

Pre-processing

Here we’ll load in the data. Fortunately, this data is quite neatly organised. When present, we can load the .h5 object. Please see the Archive page of the website to find vignettes on downloading and installing GEO-sourced objects that are not as well-organised!

We will:

Load in the data

We’ll load in the supplied metadata, and then we can load 10X data quite easily using the Load10X_Spatial function, which takes a certain file format. We can load in the filtered matrix, which is filtered based on whether the initial processing detected that that spot aligned with tissue. Alternatively, you can load the raw object, and filter as you wish.

meta <- read.csv("raw/GSE290350_metadata.csv.gz")
data <- Load10X_Spatial(
  data.dir="raw/GSE290350_RAW/GSM8811056_P004/P004",
  filename = "filtered_feature_bc_matrix.h5",
  filter.matrix = F
)

Add meta data

The metadata provided is not found in the object itself. So we will run a small piece of code that lets you add metadata to a Seurat object.

data$ID <- "P004"
data <- AddMetaData(data,
                    data@meta.data %>% 
                      right_join(meta[meta$ID=="P004", ], by="ID"))

QC

There are several ways to QC transcriptomic data. First, you should check and scrutinise the original manuscript.

We will assess:

  • number of genes per spot

  • number of unique genes per spot

  • mitochondrial, haemoglobin, and ribosomal percentage.

We’ll also filter the genes themselves, to remove noisy/pointless genes.

  • genes that are not present in more than x number of spots

  • high-expressing genes across spots.

Genes per spot & Unique genes per spot

This is a typical single-cell RNA analysis metric. It should be carefully considered how this data is interpretted and filtered, as this is not single cell. Spots can have double the average or half the average gene counts based on the density of cells. Indeed, this is a consideration for normalisation, but this consideration is for another workshop.

As there is no gold standard, we will demonstrate how you visualise the QC metrics and then filter.

p1=FeatureScatter(data, "nCount_Spatial", "nFeature_Spatial")+NoLegend()
# number of genes
p2=SpatialFeaturePlot(data, "nCount_Spatial", 
                   crop=F,
                   pt.size=2,
                   alpha=c(.2,1))+
  NoLegend()
# number of unique features
p3=SpatialFeaturePlot(data, "nFeature_Spatial", 
                   crop=F,
                   pt.size=2,
                   alpha=c(.2,1))+
  NoLegend()
plot_grid(p1,plot_grid(p2,p3, nrow=2))

Version Author Date
fb553e5 DrThomasOneil 2025-05-14

The minimum number of Features per spot (107) and minimum number of Counts per spot (151) are greater than the paper’s cut off (which is 100, presuming they did not use the filtered matrix). However, for the purposes of demonstrating how this is done:

data <- subset(data, 
               subset = nCount_Spatial > 100 & nFeature_Spatial >100)

Module Scoring

In this paper, they remove cells that are >15% Mt genes & > 10% Hb reads.

data <- PercentageFeatureSet(data, "^MT-", col.name = "percent_mito")
data <- PercentageFeatureSet(data, "^HB[^(P)]", col.name = "percent_hb")
data <- PercentageFeatureSet(data, "^RP[SL]", col.name = "percent_ribo")

Statistical and visual summaries of the QC metrics.

feature <-  c("nCount_Spatial", "nFeature_Spatial","percent_mito","percent_hb", "percent_ribo")
sapply(data@meta.data[feature], summary) %>% 
  as_tibble(rownames = "stat") %>% 
  knitr::kable(digits = 1)
stat nCount_Spatial nFeature_Spatial percent_mito percent_hb percent_ribo
Min. 151.0 107.0 1.9 0.0 2.3
1st Qu. 1068.2 680.0 5.4 0.0 8.0
Median 2644.5 1363.0 6.5 0.0 9.6
Mean 6114.8 1934.1 6.7 0.1 9.9
3rd Qu. 6933.2 2584.2 7.8 0.1 11.8
Max. 70736.0 9935.0 14.6 12.1 20.9
p1=SpatialFeaturePlot(data, "percent_mito", 
                   crop=F,
                   pt.size=2,
                   alpha=c(.2,1))+
  NoLegend()+ggtitle("Mito")
p2=SpatialFeaturePlot(data, "percent_hb", 
                   crop=F,
                   pt.size=2,
                   alpha=c(.2,1))+
  NoLegend()+ggtitle("Hb-genes")
p3=SpatialFeaturePlot(data, "percent_ribo", 
                   crop=F,
                   pt.size=2,
                   alpha=c(.2,1))+
  NoLegend()+ggtitle("Ribo")
plot_grid(p1,p2,p3)

Version Author Date
fb553e5 DrThomasOneil 2025-05-14

Filter:

data <- subset(data, subset = percent_mito<15 & percent_hb <10)
sapply(data@meta.data[feature], summary) %>% 
  as_tibble(rownames = "stat") %>% 
  knitr::kable(digits = 1)
stat nCount_Spatial nFeature_Spatial percent_mito percent_hb percent_ribo
Min. 151.0 107.0 1.9 0.0 2.3
1st Qu. 1067.5 681.0 5.4 0.0 8.0
Median 2654.0 1363.0 6.5 0.0 9.6
Mean 6118.8 1935.1 6.7 0.1 9.9
3rd Qu. 6952.5 2585.5 7.8 0.1 11.8
Max. 70736.0 9935.0 14.6 4.7 20.9

Filter genes

We’ll first remove certain genes. MALAT1 is highly expressed in long non-coding RNA and is pretty ubiquitous. So we dont want to include and subsequently cluster according to the expression of this gene. Similarly, Hb genes are highly expressed in rbc, which may represent contamination. We can remove these too.

keep_genes <- rownames(data)[!rownames(data) %in% c(grep("MALAT1", rownames(data), value=T), grep("^HB[^(P)]", rownames(data), value=T))]
data <- subset(data, features = keep_genes)

Low genes can create noise and take up unnecessary space in the object. Let’s remove genes according to the original manuscript, whereby a gene not found in > 2 spots is removed. You can adjust this as you wish.

table(rowSums(data@assays$Spatial@layers$counts>=1)>=2)

FALSE  TRUE 
17971 18617 
keep_genes <- rowSums(data@assays$Spatial@layers$counts >= 1) >= 2
#before filtering
dim(data)
[1] 36588  1255
data <- subset(data, features = rownames(data)[keep_genes])
# after filtering
dim(data)
[1] 18617  1255

We recommend taking some time to carefully consider your QC strategy. There is no universal gold standard. One common approach is to start with lenient QC, proceed with processing, and during clustering and annotation, remain aware of the relaxed filtering. If unexpected results arise, you can revisit and refine your QC. Personally, I prefer to be permissive at first, while tagging potential outliers for future consideration. For example, we initially filtered out genes not present in at least 2 spots, but I may also label genes present in fewer than 5 spots as “toQC” for later review—particularly when assessing differential expression results. Similarly, we applied a 15% mitochondrial threshold but did not filter based on ribosomal content. At a later stage, we could tighten the mitochondrial threshold to 10% and include a filter for high ribosomal spots. If unusual clusters appear, we can then assess how many of those cells would have been excluded under stricter QC.

Data Processing

Seurat is an efficient package with simple functions for standard processing.

Normalisation

Again, normalisation is quite simple.

data <- NormalizeData(data, 
                      normalization.method = "LogNormalize",
                      verbose=F)

Variable Features

We’ll find the top variable features.

data <- FindVariableFeatures(data, 
                             method = 'vst', 
                             nfeatures = 2000, # Find the 2000 most variable features
                             verbose=F)

We can output/visualise them.

VariableFeatures(data) %>% head(50)
 [1] "PI3"        "SPRR1A"     "IGLC3"      "FLG"        "IGHA2"     
 [6] "IGLC2"      "IGHA1"      "JCHAIN"     "IGHG1"      "AC019349.1"
[11] "IGKC"       "IGLC1"      "IGHG4"      "KRT10"      "IGHM"      
[16] "MUSTN1"     "CRISP3"     "CEACAM5"    "SPINK7"     "SERPINB4"  
[21] "KRTDAP"     "SERPINB3"   "PI15"       "RPTN"       "SPRR2B"    
[26] "KLK6"       "MGST1"      "KRT6B"      "SLURP1"     "ERO1A"     
[31] "CCL14"      "KRT1"       "SPRR2E"     "RNASE7"     "TOP2A"     
[36] "CRCT1"      "MT1X"       "RRM2"       "S100A7"     "DUOXA2"    
[41] "STMN1"      "LCE3D"      "PCNA"       "FAM25A"     "IGHG3"     
[46] "C15orf48"   "OLFM4"      "PTTG1"      "YOD1"       "CEACAM7"   

LabelPoints(plot = VariableFeaturePlot(data), 
            points = head(VariableFeatures(data), 50), repel = TRUE)

Version Author Date
fb553e5 DrThomasOneil 2025-05-14

Scale Data

Scaling the data is also easy!

data <- ScaleData(data, 
                  features = VariableFeatures(data), #if you want to scale all features, change this to rownames(data)
        verbose=F)

If you need to return to your analysis at another time, or you want a record, we can save the processed data in a dedicated folder and label it accordingly.

saveRDS(data, file = paste0("data/SeuratObject_",format.Date(Sys.Date(),"%Y%m%d"),"_filtered_normalised_scaled.rds"))

PCA & UMAP

Also very easy with Seurat. We’ll determine the number of PCs to use for the UMAP using ElbowPlot

data <- RunPCA(data,
               npcs = 50,
               verbose=F)

ElbowPlot(data)

Version Author Date
c0fa3bc DrThomasOneil 2025-05-12

The elbow plot drops off drastically and plateaus at ~6, meaning the variation just becomes noisy around PC6-onwards. Or, more than 6 PCs do not represent the data any better. So we’ll just use the first 6 to proceed.

data <- RunUMAP(data, 
                dims=1:6, 
                verbose=F)

Visualisation

There are several visualisations we can do:

Graph:

Spatial:

UMAP

UMAP Visualisation

We’ll first cluster the data.


data <- data %>% 
  FindNeighbors(dims = 1:6, verbose=F) %>%
  FindClusters(resolution=1, verbose=F)

And now we can create a UMAP.

p1=UMAPPlot(data,
            label=T, label.box=T)+
  NoLegend()+
  NoAxes()+
  ggtitle("11 Clusters")
p1

Version Author Date
fb553e5 DrThomasOneil 2025-05-14
Open to view more Plotting options

Here are some additional arguments that change the default visualisations. Chop and change to see how they work:

UMAPPlot(data, 
         pt.size=2, 
         label=T, 
         label.size=10,
         label.box=T,
         label.color="white",
         repel=T)+
  NoLegend()+
  NoAxes()+
  scale_color_manual(values= c("blue4", 'blue2', 'lightblue', 'green2', 'green4','brown', 'orange', 'red', 'red3', 'red4', 'black'))+
  scale_fill_manual(values= c("blue4", 'blue2', 'lightblue', 'green2', 'green4','brown', 'orange', 'red', 'red3', 'red4', 'black'))

Version Author Date
c0fa3bc DrThomasOneil 2025-05-12

Featureplots

We can view the gene expression on the UMAP itself.

p2=FeaturePlot(data, 
            "CD3E",
            order=T,
            pt.size=2, 
            cols = c("yellow2", 'blue3'))+NoAxes()+NoLegend()
p3=FeaturePlot(data, 
            "F13A1",
            order=T,
            pt.size=2, 
            cols = c("yellow2", 'blue3'))+NoAxes()+NoLegend()
p4=FeaturePlot(data, 
            "COL1A1",
            order=T,
            pt.size=2, 
            cols = c("yellow2", 'blue3'))+NoAxes()+NoLegend()
p5=FeaturePlot(data, 
            "KRT5",
            order=T,
            pt.size=2,
            min.cutoff = 3,
            cols = c("yellow2", 'blue3'))+NoAxes()+NoLegend()
plot_grid(p1, plot_grid(p2,p3,p4,p5, ncol=2))

Version Author Date
c0fa3bc DrThomasOneil 2025-05-12

# Can save the plot
psave <- plot_grid(p1, plot_grid(p2,p3,p4,p5, ncol=2))
ggsave("FeaturePlot_UMAP.png", plot = psave, path = "./plots", width = 18, height = 10, units = c("cm"))

DE and DotPlots

And we can simply assess the clusters themselves using differential analysis and expression graphs.

markers <- FindAllMarkers(data, 
                          logfc.threshold = 0.1,#default - increasing speeds it up, but may miss weaker signals.
                          min.pct = 0.01, #default - can be increased to ensure that the minimum expression of genes are considered. 
                          min.diff.pct = -Inf, # you can adjust this too. IF you want there to be at least a 10% difference in percentage of spots expressing the gene, you'd change this to 0.1. Can ensure that its not only the level of expression, but that you're capturing highly expressed genes
                          verbose=F #change this to true if you want to track the speed at which DE is being calculated. It is off here to save it outputting into the document 
                          ) 
top10 <- markers %>% 
  group_by(cluster) %>% 
  top_n(wt=avg_log2FC, n=10)
Heatmap
DoHeatmap(AggregateExpression(ScaleData(data, rownames(data), verbose=F), return.seurat = T, verbose=F),
          features = top10$gene, 
          draw.lines = F)

Version Author Date
fb553e5 DrThomasOneil 2025-05-14
c0fa3bc DrThomasOneil 2025-05-12

DotPlot
DotPlot(data, 
        features = c("CD3E", "CLEC10A", "CD207", "KRT5", "COL1A1"), 
        cols=c("grey", "red"))

Version Author Date
fb553e5 DrThomasOneil 2025-05-14
c0fa3bc DrThomasOneil 2025-05-12
Violin
VlnPlot(data, 
        features = c("CD3E", "CLEC10A", "CD207", "KRT5", "COL1A1"))

Version Author Date
fb553e5 DrThomasOneil 2025-05-14
c0fa3bc DrThomasOneil 2025-05-12
VlnPlot(data, 
        features = c("CD3E", "CLEC10A", "CD207", "KRT5", "COL1A1"), stack=T)

Version Author Date
fb553e5 DrThomasOneil 2025-05-14
c0fa3bc DrThomasOneil 2025-05-12

Spatial

We can do a few things here.

Clusters overlaid spatially

SpatialDimPlot(data, pt.size=3)

Version Author Date
fb553e5 DrThomasOneil 2025-05-14
c0fa3bc DrThomasOneil 2025-05-12
ImageDimPlot(data, size=3)

Version Author Date
fb553e5 DrThomasOneil 2025-05-14
c0fa3bc DrThomasOneil 2025-05-12

Expressions overlaid spatially

SpatialFeaturePlot(data, 
                   features="CD3E",
                   pt.size=3)

Version Author Date
fb553e5 DrThomasOneil 2025-05-14

SpatialFeaturePlot(data, 
                   features="CD3E",
                   pt.size=3)+
  scale_fill_viridis_b()

Version Author Date
fb553e5 DrThomasOneil 2025-05-14

ImageFeaturePlot(data, 
                   features="CD3E",
                   size=3)

Version Author Date
fb553e5 DrThomasOneil 2025-05-14

sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS Sonoma 14.3

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Australia/Sydney
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] hdf5r_1.3.12       cowplot_1.1.3      lubridate_1.9.4    forcats_1.0.0     
 [5] stringr_1.5.1      dplyr_1.1.4        purrr_1.0.4        readr_2.1.5       
 [9] tidyr_1.3.1        tibble_3.2.1       ggplot2_3.5.1      tidyverse_2.0.0   
[13] Seurat_5.2.1       SeuratObject_5.0.2 sp_2.2-0           cli_3.6.4         
[17] workflowr_1.7.1   

loaded via a namespace (and not attached):
  [1] RColorBrewer_1.1-3     rstudioapi_0.17.1      jsonlite_1.9.1        
  [4] magrittr_2.0.3         ggbeeswarm_0.7.2       spatstat.utils_3.1-3  
  [7] farver_2.1.2           rmarkdown_2.29         ragg_1.3.3            
 [10] fs_1.6.5               vctrs_0.6.5            ROCR_1.0-11           
 [13] spatstat.explore_3.3-4 htmltools_0.5.8.1      sass_0.4.9            
 [16] sctransform_0.4.1      parallelly_1.42.0      KernSmooth_2.23-26    
 [19] bslib_0.9.0            htmlwidgets_1.6.4      ica_1.0-3             
 [22] plyr_1.8.9             plotly_4.10.4          zoo_1.8-13            
 [25] cachem_1.1.0           whisker_0.4.1          igraph_2.1.4          
 [28] mime_0.13              lifecycle_1.0.4        pkgconfig_2.0.3       
 [31] Matrix_1.7-3           R6_2.6.1               fastmap_1.2.0         
 [34] fitdistrplus_1.2-2     future_1.34.0          shiny_1.10.0          
 [37] digest_0.6.37          colorspace_2.1-1       patchwork_1.3.0       
 [40] ps_1.9.0               rprojroot_2.0.4        tensor_1.5            
 [43] RSpectra_0.16-2        irlba_2.3.5.1          textshaping_1.0.0     
 [46] labeling_0.4.3         progressr_0.15.1       timechange_0.3.0      
 [49] spatstat.sparse_3.1-0  httr_1.4.7             polyclip_1.10-7       
 [52] abind_1.4-8            compiler_4.4.0         bit64_4.6.0-1         
 [55] withr_3.0.2            fastDummies_1.7.5      MASS_7.3-65           
 [58] tools_4.4.0            vipor_0.4.7            lmtest_0.9-40         
 [61] beeswarm_0.4.0         httpuv_1.6.15          future.apply_1.11.3   
 [64] goftest_1.2-3          glue_1.8.0             callr_3.7.6           
 [67] nlme_3.1-167           promises_1.3.2         grid_4.4.0            
 [70] Rtsne_0.17             getPass_0.2-4          cluster_2.1.8.1       
 [73] reshape2_1.4.4         generics_0.1.3         gtable_0.3.6          
 [76] spatstat.data_3.1-6    tzdb_0.5.0             hms_1.1.3             
 [79] data.table_1.17.0      spatstat.geom_3.3-6    RcppAnnoy_0.0.22      
 [82] ggrepel_0.9.6          RANN_2.6.2             pillar_1.10.1         
 [85] limma_3.60.6           spam_2.11-1            RcppHNSW_0.6.0        
 [88] later_1.4.1            splines_4.4.0          lattice_0.22-6        
 [91] bit_4.6.0              survival_3.8-3         deldir_2.0-4          
 [94] tidyselect_1.2.1       miniUI_0.1.1.1         pbapply_1.7-2         
 [97] knitr_1.50             git2r_0.35.0           gridExtra_2.3         
[100] scattermore_1.2        xfun_0.51              statmod_1.5.0         
[103] matrixStats_1.5.0      stringi_1.8.4          lazyeval_0.2.2        
[106] yaml_2.3.10            evaluate_1.0.3         codetools_0.2-20      
[109] uwot_0.2.3             systemfonts_1.2.1      xtable_1.8-4          
[112] reticulate_1.41.0.1    munsell_0.5.1          processx_3.8.6        
[115] jquerylib_0.1.4        Rcpp_1.0.14            globals_0.16.3        
[118] spatstat.random_3.3-2  png_0.1-8              ggrastr_1.0.2         
[121] spatstat.univar_3.1-2  parallel_4.4.0         presto_1.0.0          
[124] dotCall64_1.2          listenv_0.9.1          viridisLite_0.4.2     
[127] scales_1.3.0           ggridges_0.5.6         rlang_1.1.6           
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIFZpc2l1bSBBbmFseXNpcyIKYXV0aG9yOiAiQXV0aG9yIGFuZCBEZW1vbnN0cmF0b3I6IFRob21hcyBPJ05laWwiCmRhdGU6ICIyMDI1LTA1IiAKb3V0cHV0OgogIHdvcmtmbG93cjo6d2Zsb3dfaHRtbDoKICAgIHRvYzogZmFsc2UKICAgIGNvZGVfZm9sZGluZzogImhpZGUiCgotLS0KIApgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmc9RkFMU0UsIGVycm9yPUZBTFNFLCBjb2xsYXBzZT1UKQpgYGAKCjxkaXYgc3R5bGUgPSAidGV4dC1hbGlnbjogY2VudGVyIj4KCjxyZWFkLXRpbWU+PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZTogMS4zZW0iPlBsZWFzZSB0cnkgdG8gZm9sbG93IHRoZSBpbnN0cnVjdGlvbnMgYmVsb3cgdG8gc2V0IHVwIHlvdXIgYW5hbHlzaXMgKipwcmlvcioqIHRvIGF0dGVuZGluZyB0aGUgd29ya3Nob3AuPGJyPjxicj48aT4qKkJlIGF3YXJlISoqPC9pPjwvc3Bhbj48YnI+IEl0IGlzIGRpZmZpY3VsdCB0byBkZWJ1ZyBvdmVyIHpvb20uIDxicj5JZiB5b3Ugd2FudCB0aGUgYmVzdCBmZWVkYmFjayBhbmQgZXhwZXJpZW5jZSwgbWFrZSBzdXJlIHlvdSBhdHRlbmQgdGhlIHdvcmtzaG9wIGluIHBlcnNvbiE8L3JlYWQtdGltZT4KCllvdSBjYW4gZG93bmxvYWQgdGhlIHNjcmlwdCBvbiB0aGUgdG9wIHJpZ2h0IG9yIGZyb20gW3RoaXMgbGlua10oaHR0cHM6Ly9naXRodWIuY29tL0RyVGhvbWFzT25laWwvRGlnaXRhbC1SZXNlYXJjaC1Ta2lsbHMtTmV0d29yay9ibG9iL21haW4vYW5hbHlzaXMvMjAyNTA1X1dvcmtzaG9wLlJtZCkuCiAKPC9kaXY+PHdpbXI+CgoKIyBJbnRyb2R1Y3Rpb24KCiMgU2V0dXB7LnRhYnNldCAudGFic2V0LWZhZGV9CgotIENyZWF0ZSBmb2xkZXJzCgotIEluc3RhbGwgUGFja2FnZXMKCi0gRG93bmxvYWQgRGF0YQoKIyMgQ3JlYXRlIEZvbGRlcnMKCmBgYHRleHQK4pSc4pSA4pSAIFlvdXJGb2xkZXIK4pSCICAgICDilJTilIDilIAgcmF3ICh3aGVyZSB3ZSBjYW4gZGVwb3NpdCByYXcgZG93bmxvYWRlZCBkYXRhKQrilIIgICAgIOKUlOKUgOKUgCBkYXRhICh3aGVyZSB3ZSBjYW4gcHJvY2VzcyBkYXRhIGFuZCBzdG9yZSBpdCkK4pSCICAgICDilJTilIDilIAgcGxvdHMgKHdoZXJlIHdlIGNhbiBzdG9yZSBwbG90cykKYGBgCgpgYGB7ciBldmFsPUZ9CmlmKCFkaXIuZXhpc3RzKCJyYXciKSl7ZGlyLmNyZWF0ZSgicmF3Iil9CmlmKCFkaXIuZXhpc3RzKCJkYXRhIikpe2Rpci5jcmVhdGUoImRhdGEiKX0KaWYoIWRpci5leGlzdHMoInBsb3RzIikpe2Rpci5jcmVhdGUoInBsb3RzIil9CmBgYAoKPHdpbXI+CgojIyBJbnN0YWxsIFBhY2thZ2VzCgpgYGB7ciwgZXZhbD1GfQppbnN0YWxsLnBhY2thZ2VzKCJTZXVyYXQiKQppbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQppbnN0YWxsLnBhY2thZ2VzKCJjb3dwbG90IikKaW5zdGFsbC5wYWNrYWdlcygiaGRmNXIiKQoKYGBgCgpXZSBoYXZlIGEgZnVuY3Rpb24gdGhhdCB3aWxsIGxldCB5b3UgY2hlY2sgdGhlIHNldHVwCmBgYHtyfQpzb3VyY2UoImh0dHBzOi8vZ2l0aHViLmNvbS9EclRob21hc09uZWlsL0RpZ2l0YWwtUmVzZWFyY2gtU2tpbGxzLU5ldHdvcmsvcmF3L3JlZnMvaGVhZHMvbWFpbi9kb2NzL2FkaXQvY2hlY2tzZXR1cC5SIikKCmNoZWNrU2V0dXAoCiAgY3Jhbl9wYWNrYWdlcyA9IGMoIlNldXJhdCIsICJ0aWR5dmVyc2UiLCAiY293cGxvdCIsImhkZjVyIiksCiAgYmlvY19wYWNrYWdlcyA9IGMoKQogICkKCnNldC5zZWVkKDEzMzcpCmBgYAoKPHdpbXI+CgojIyBEb3dubG9hZCBkYXRhCgpXZSB3aWxsIGRvd25sb2FkIHRoZSBkYXRhIGRpcmVjdGx5IGZyb20gdGhlIFtHRU9dKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvZ2VvLykuIFdlJ2xsIHVzZSBbdGhpc10oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9nZW8vcXVlcnkvYWNjLmNnaT9hY2M9R1NFMjkwMzUwKSBkYXRhc2V0LCB3aGljaCB3YXMgW3B1Ymxpc2hlZCBpbiAyMDI0XShodHRwczovL3BtYy5uY2JpLm5sbS5uaWguZ292L2FydGljbGVzL1BNQzExNjQ2ODU1LykuIEFkZGl0aW9uYWxseSwgdGhpcyBncm91cCBoYXZlIHB1Ymxpc2hlZCBbYWxsIG9mIHRoZWlyIG1ldGhvZHMgYW5kIHNjcmlwdHMgZm9yIGFuYWx5c2lzXShodHRwczovL2dpdGh1Yi5jb20vdmlsZGVrYS9TcGF0aWFsX0RNUEE/dGFiPXJlYWRtZS1vdi1maWxlKS4KCmBgYHtyLCBldmFsPUZ9CiMgR28gdG8gdGhlIEdFTyBwYWdlIGFuZCByaWdodCBjbGljayBvbiB0aGUgaHR0cCBsaW5rIHVuZGVyIGRvd25sb2FkLiBJdCBzaG91bGQgbG9vayBsaWtlIHRoaXMKCmRhdGFfZmlsZSA8LSAiaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9nZW8vZG93bmxvYWQvP2FjYz1HU0UyOTAzNTAmZm9ybWF0PWZpbGUiCgojIGRvd25sb2FkIHRoZSBkYXRhCmlmKCFmaWxlLmV4aXN0cygicmF3L0dTRTI5MDM1MF9SQVcudGFyIikpe2Rvd25sb2FkLmZpbGUodXJsPWRhdGFfZmlsZSwgZGVzdGZpbGUgPSAiLi9yYXcvR1NFMjkwMzUwX1JBVy50YXIiLCBtZXRob2Q9J2N1cmwnKX0KCiMgcmVwZWF0IGZvciBtZXRhZGF0YQppZighZmlsZS5leGlzdHMoInJhdy9HU0UyOTAzNTBfbWV0YWRhdGEuY3N2Lmd6Iikpe2Rvd25sb2FkLmZpbGUodXJsPSdodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L2dlby9kb3dubG9hZC8/YWNjPUdTRTI5MDM1MCZmb3JtYXQ9ZmlsZSZmaWxlPUdTRTI5MDM1MCU1Rm1ldGFkYXRhJTJFY3N2JTJFZ3onLCBkZXN0ZmlsZSA9ICJyYXcvR1NFMjkwMzUwX21ldGFkYXRhLmNzdi5neiIsIG1ldGhvZD0nY3VybCcpfQoKIyBhbmQgdGhlIHN1cHBvc2VkIHByb2Nlc3NlZCBTZXVyYXQgZGF0YQppZighZmlsZS5leGlzdHMoInJhdy9HU0UyOTAzNTBfc2V1cmF0T2JqX3NwYXRpYWxfZGlzdC5SRFMiKSl7ZG93bmxvYWQuZmlsZSh1cmw9Imh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvZ2VvL2Rvd25sb2FkLz9hY2M9R1NFMjkwMzUwJmZvcm1hdD1maWxlJmZpbGU9R1NFMjkwMzUwJTVGc2V1cmF0T2JqJTVGc3BhdGlhbCU1RmRpc3QlMkVSRFMiLCBkZXN0ZmlsZSA9ICJyYXcvR1NFMjkwMzUwX3NldXJhdE9ial9zcGF0aWFsX2Rpc3QuUkRTIiwgbWV0aG9kPSdjdXJsJyl9CgpgYGAKCjx3aW1yPgoKIyMgVW56aXAgYW5kIHVudGFyIHRoZSByYXcgZGF0YQoKYGBge3IsIGV2YWw9Rn0KdW50YXIoInJhdy9HU0UyOTAzNTBfUkFXLnRhciIsIGV4ZGlyID0gIi4vcmF3L0dTRTI5MDM1MF9SQVciKQpgYGAKCmBgYHtyLCBldmFsPUZ9CnNhbXBsZXMgPC0gbGlzdC5maWxlcygicmF3L0dTRTI5MDM1MF9SQVciLCBwYXR0ZXJuID0gIlxcLnRhclxcLmd6JCIsIGZ1bGwubmFtZXMgPSBUUlVFKQoKZm9yIChpIGluIHNlcV9hbG9uZyhzYW1wbGVzKSkgewogIHRhcl9wYXRoIDwtIHN1YigiXFwuZ3okIiwgIiIsIHNhbXBsZXNbaV0pCgogICMgVW56aXAgdGhlIC50YXIuZ3ogZmlsZQogIFIudXRpbHM6Omd1bnppcChzYW1wbGVzW2ldLCBkZXN0bmFtZSA9IHRhcl9wYXRoLCBvdmVyd3JpdGUgPSBUUlVFKQoKICBvdXRwdXRfZGlyIDwtIGZpbGUucGF0aCgicmF3L0dTRTI5MDM1MF9SQVciLCBzdWIoIlxcLnRhciQiLCAiIiwgYmFzZW5hbWUodGFyX3BhdGgpKSkKICBkaXIuY3JlYXRlKG91dHB1dF9kaXIsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQoKICAjIEV4dHJhY3QgdGFyIHRvIG91dHB1dCBkaXJlY3RvcnkKICB1bnRhcih0YXJfcGF0aCwgZXhkaXIgPSBvdXRwdXRfZGlyKQp9CmBgYAoKPHdpbXI+CgojIFByZS1wcm9jZXNzaW5ney50YWJzZXQgLnRhYnNldC1mYWRlfQoKSGVyZSB3ZSdsbCBsb2FkIGluIHRoZSBkYXRhLiBGb3J0dW5hdGVseSwgdGhpcyBkYXRhIGlzIHF1aXRlIG5lYXRseSBvcmdhbmlzZWQuIFdoZW4gcHJlc2VudCwgd2UgY2FuIGxvYWQgdGhlIGAuaDVgIG9iamVjdC4gUGxlYXNlIHNlZSB0aGUgYEFyY2hpdmVgIHBhZ2Ugb2YgdGhlIFt3ZWJzaXRlXShodHRwczovL2RpZ2l0YWxyZXNlYXJjaHNraWxscy5uZXR3b3JrLzBfYXJjaGl2ZS5odG1sKSB0byBmaW5kIHZpZ25ldHRlcyBvbiBkb3dubG9hZGluZyBhbmQgaW5zdGFsbGluZyBHRU8tc291cmNlZCBvYmplY3RzIHRoYXQgYXJlIG5vdCBhcyB3ZWxsLW9yZ2FuaXNlZCEKCldlIHdpbGw6CgotIExvYWQgdGhlIGRhdGEKCi0gQWRkIHRoZSBtZXRhIGRhdGEKCi0gQ2hlY2sgdGhlIFFDLCBmaWx0ZXIgYW5kIHByb2Nlc3MgdGhlIGRhdGEKCjxkaXY+CgojIyBMb2FkIGluIHRoZSBkYXRhIAoKV2UnbGwgbG9hZCBpbiB0aGUgc3VwcGxpZWQgbWV0YWRhdGEsIGFuZCB0aGVuIHdlIGNhbiBsb2FkIDEwWCBkYXRhIHF1aXRlIGVhc2lseSB1c2luZyB0aGUgYExvYWQxMFhfU3BhdGlhbGAgZnVuY3Rpb24sIHdoaWNoIHRha2VzIGEgY2VydGFpbiBmaWxlIGZvcm1hdC4gV2UgY2FuIGxvYWQgaW4gdGhlIGZpbHRlcmVkIG1hdHJpeCwgd2hpY2ggaXMgZmlsdGVyZWQgYmFzZWQgb24gd2hldGhlciB0aGUgaW5pdGlhbCBwcm9jZXNzaW5nIGRldGVjdGVkIHRoYXQgdGhhdCBzcG90IGFsaWduZWQgd2l0aCB0aXNzdWUuIEFsdGVybmF0aXZlbHksIHlvdSBjYW4gbG9hZCB0aGUgcmF3IG9iamVjdCwgYW5kIGZpbHRlciBhcyB5b3Ugd2lzaC4gCgpgYGB7cn0KbWV0YSA8LSByZWFkLmNzdigicmF3L0dTRTI5MDM1MF9tZXRhZGF0YS5jc3YuZ3oiKQpgYGAKCmBgYHtyIGxvYWR9CmRhdGEgPC0gTG9hZDEwWF9TcGF0aWFsKAogIGRhdGEuZGlyPSJyYXcvR1NFMjkwMzUwX1JBVy9HU004ODExMDU2X1AwMDQvUDAwNCIsCiAgZmlsZW5hbWUgPSAiZmlsdGVyZWRfZmVhdHVyZV9iY19tYXRyaXguaDUiLAogIGZpbHRlci5tYXRyaXggPSBGCikKYGBgCgo8d2ltcj4KCiMjIEFkZCBtZXRhIGRhdGEKClRoZSBtZXRhZGF0YSBwcm92aWRlZCBpcyBub3QgZm91bmQgaW4gdGhlIG9iamVjdCBpdHNlbGYuIFNvIHdlIHdpbGwgcnVuIGEgc21hbGwgcGllY2Ugb2YgY29kZSB0aGF0IGxldHMgeW91IGFkZCBtZXRhZGF0YSB0byBhIFNldXJhdCBvYmplY3QuIAoKYGBge3J9CmRhdGEkSUQgPC0gIlAwMDQiCmRhdGEgPC0gQWRkTWV0YURhdGEoZGF0YSwKICAgICAgICAgICAgICAgICAgICBkYXRhQG1ldGEuZGF0YSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICByaWdodF9qb2luKG1ldGFbbWV0YSRJRD09IlAwMDQiLCBdLCBieT0iSUQiKSkKYGBgCgoKIyMgUUN7LnRhYnNldCAudGFic2V0LWZhZGV9CgpUaGVyZSBhcmUgc2V2ZXJhbCB3YXlzIHRvIFFDIHRyYW5zY3JpcHRvbWljIGRhdGEuIEZpcnN0LCB5b3Ugc2hvdWxkIGNoZWNrIGFuZCBzY3J1dGluaXNlIHRoZSBbb3JpZ2luYWwgbWFudXNjcmlwdF0oaHR0cHM6Ly9naXRodWIuY29tL3ZpbGRla2EvU3BhdGlhbF9ETVBBP3RhYj1yZWFkbWUtb3YtZmlsZSkuCgpXZSB3aWxsIGFzc2VzczogCgotIG51bWJlciBvZiBnZW5lcyBwZXIgc3BvdAoKLSBudW1iZXIgb2YgdW5pcXVlIGdlbmVzIHBlciBzcG90CgotIG1pdG9jaG9uZHJpYWwsIGhhZW1vZ2xvYmluLCBhbmQgcmlib3NvbWFsIHBlcmNlbnRhZ2UuCgpXZSdsbCBhbHNvIGZpbHRlciB0aGUgZ2VuZXMgdGhlbXNlbHZlcywgdG8gcmVtb3ZlIG5vaXN5L3BvaW50bGVzcyBnZW5lcy4gCgotIGdlbmVzIHRoYXQgYXJlIG5vdCBwcmVzZW50IGluIG1vcmUgdGhhbiB4IG51bWJlciBvZiBzcG90cwoKLSBoaWdoLWV4cHJlc3NpbmcgZ2VuZXMgYWNyb3NzIHNwb3RzLgoKIyMjIEdlbmVzIHBlciBzcG90ICYgVW5pcXVlIGdlbmVzIHBlciBzcG90CgpUaGlzIGlzIGEgdHlwaWNhbCBgc2luZ2xlLWNlbGwgUk5BYCBhbmFseXNpcyBtZXRyaWMuIEl0IHNob3VsZCBiZSBjYXJlZnVsbHkgY29uc2lkZXJlZCBob3cgdGhpcyBkYXRhIGlzIGludGVycHJldHRlZCBhbmQgZmlsdGVyZWQsIGFzIHRoaXMgaXMgKipub3QqKiBzaW5nbGUgY2VsbC4gU3BvdHMgY2FuIGhhdmUgZG91YmxlIHRoZSBhdmVyYWdlIG9yIGhhbGYgdGhlIGF2ZXJhZ2UgZ2VuZSBjb3VudHMgYmFzZWQgb24gdGhlIGRlbnNpdHkgb2YgY2VsbHMuIEluZGVlZCwgdGhpcyBpcyBhIGNvbnNpZGVyYXRpb24gZm9yIG5vcm1hbGlzYXRpb24sIGJ1dCB0aGlzIGNvbnNpZGVyYXRpb24gaXMgZm9yIGFub3RoZXIgd29ya3Nob3AuIAoKPGk+KipBcyB0aGVyZSBpcyA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5ubzwvc3Bhbj4gZ29sZCBzdGFuZGFyZCoqPC9pPiwgd2Ugd2lsbCBkZW1vbnN0cmF0ZSBob3cgeW91IHZpc3VhbGlzZSB0aGUgUUMgbWV0cmljcyBhbmQgdGhlbiBmaWx0ZXIuIAoKYGBge3J9CnAxPUZlYXR1cmVTY2F0dGVyKGRhdGEsICJuQ291bnRfU3BhdGlhbCIsICJuRmVhdHVyZV9TcGF0aWFsIikrTm9MZWdlbmQoKQojIG51bWJlciBvZiBnZW5lcwpwMj1TcGF0aWFsRmVhdHVyZVBsb3QoZGF0YSwgIm5Db3VudF9TcGF0aWFsIiwgCiAgICAgICAgICAgICAgICAgICBjcm9wPUYsCiAgICAgICAgICAgICAgICAgICBwdC5zaXplPTIsCiAgICAgICAgICAgICAgICAgICBhbHBoYT1jKC4yLDEpKSsKICBOb0xlZ2VuZCgpCiMgbnVtYmVyIG9mIHVuaXF1ZSBmZWF0dXJlcwpwMz1TcGF0aWFsRmVhdHVyZVBsb3QoZGF0YSwgIm5GZWF0dXJlX1NwYXRpYWwiLCAKICAgICAgICAgICAgICAgICAgIGNyb3A9RiwKICAgICAgICAgICAgICAgICAgIHB0LnNpemU9MiwKICAgICAgICAgICAgICAgICAgIGFscGhhPWMoLjIsMSkpKwogIE5vTGVnZW5kKCkKcGxvdF9ncmlkKHAxLHBsb3RfZ3JpZChwMixwMywgbnJvdz0yKSkKYGBgCgpUaGUgbWluaW11bSBudW1iZXIgb2YgRmVhdHVyZXMgcGVyIHNwb3QgKGByIG1pbihkYXRhJG5GZWF0dXJlX1NwYXRpYWwpYCkgYW5kIG1pbmltdW0gbnVtYmVyIG9mIENvdW50cyBwZXIgc3BvdCAoYHIgbWluKGRhdGEkbkNvdW50X1NwYXRpYWwpYCkgYXJlIGdyZWF0ZXIgdGhhbiB0aGUgcGFwZXIncyBjdXQgb2ZmICh3aGljaCBpcyAxMDAsIHByZXN1bWluZyB0aGV5IGRpZCBub3QgdXNlIHRoZSBmaWx0ZXJlZCBtYXRyaXgpLiBIb3dldmVyLCBmb3IgdGhlIHB1cnBvc2VzIG9mIGRlbW9uc3RyYXRpbmcgaG93IHRoaXMgaXMgZG9uZToKCmBgYHtyfQpkYXRhIDwtIHN1YnNldChkYXRhLCAKICAgICAgICAgICAgICAgc3Vic2V0ID0gbkNvdW50X1NwYXRpYWwgPiAxMDAgJiBuRmVhdHVyZV9TcGF0aWFsID4xMDApCmBgYAoKIyMjIE1vZHVsZSBTY29yaW5nCgpJbiB0aGlzIHBhcGVyLCB0aGV5IHJlbW92ZSBjZWxscyB0aGF0IGFyZSA+MTUlIE10IGdlbmVzICYgPiAxMCUgSGIgcmVhZHMuIAoKYGBge3J9CmRhdGEgPC0gUGVyY2VudGFnZUZlYXR1cmVTZXQoZGF0YSwgIl5NVC0iLCBjb2wubmFtZSA9ICJwZXJjZW50X21pdG8iKQpkYXRhIDwtIFBlcmNlbnRhZ2VGZWF0dXJlU2V0KGRhdGEsICJeSEJbXihQKV0iLCBjb2wubmFtZSA9ICJwZXJjZW50X2hiIikKZGF0YSA8LSBQZXJjZW50YWdlRmVhdHVyZVNldChkYXRhLCAiXlJQW1NMXSIsIGNvbC5uYW1lID0gInBlcmNlbnRfcmlibyIpCmBgYAoKU3RhdGlzdGljYWwgYW5kIHZpc3VhbCBzdW1tYXJpZXMgb2YgdGhlIFFDIG1ldHJpY3MuCgpgYGB7cn0KZmVhdHVyZSA8LSAgYygibkNvdW50X1NwYXRpYWwiLCAibkZlYXR1cmVfU3BhdGlhbCIsInBlcmNlbnRfbWl0byIsInBlcmNlbnRfaGIiLCAicGVyY2VudF9yaWJvIikKc2FwcGx5KGRhdGFAbWV0YS5kYXRhW2ZlYXR1cmVdLCBzdW1tYXJ5KSAlPiUgCiAgYXNfdGliYmxlKHJvd25hbWVzID0gInN0YXQiKSAlPiUgCiAga25pdHI6OmthYmxlKGRpZ2l0cyA9IDEpCmBgYAoKYGBge3J9CnAxPVNwYXRpYWxGZWF0dXJlUGxvdChkYXRhLCAicGVyY2VudF9taXRvIiwgCiAgICAgICAgICAgICAgICAgICBjcm9wPUYsCiAgICAgICAgICAgICAgICAgICBwdC5zaXplPTIsCiAgICAgICAgICAgICAgICAgICBhbHBoYT1jKC4yLDEpKSsKICBOb0xlZ2VuZCgpK2dndGl0bGUoIk1pdG8iKQpwMj1TcGF0aWFsRmVhdHVyZVBsb3QoZGF0YSwgInBlcmNlbnRfaGIiLCAKICAgICAgICAgICAgICAgICAgIGNyb3A9RiwKICAgICAgICAgICAgICAgICAgIHB0LnNpemU9MiwKICAgICAgICAgICAgICAgICAgIGFscGhhPWMoLjIsMSkpKwogIE5vTGVnZW5kKCkrZ2d0aXRsZSgiSGItZ2VuZXMiKQpwMz1TcGF0aWFsRmVhdHVyZVBsb3QoZGF0YSwgInBlcmNlbnRfcmlibyIsIAogICAgICAgICAgICAgICAgICAgY3JvcD1GLAogICAgICAgICAgICAgICAgICAgcHQuc2l6ZT0yLAogICAgICAgICAgICAgICAgICAgYWxwaGE9YyguMiwxKSkrCiAgTm9MZWdlbmQoKStnZ3RpdGxlKCJSaWJvIikKcGxvdF9ncmlkKHAxLHAyLHAzKQpgYGAKCkZpbHRlcjoKCmBgYHtyfQpkYXRhIDwtIHN1YnNldChkYXRhLCBzdWJzZXQgPSBwZXJjZW50X21pdG88MTUgJiBwZXJjZW50X2hiIDwxMCkKc2FwcGx5KGRhdGFAbWV0YS5kYXRhW2ZlYXR1cmVdLCBzdW1tYXJ5KSAlPiUgCiAgYXNfdGliYmxlKHJvd25hbWVzID0gInN0YXQiKSAlPiUgCiAga25pdHI6OmthYmxlKGRpZ2l0cyA9IDEpCmBgYAoKIyMjIEZpbHRlciBnZW5lcwoKV2UnbGwgZmlyc3QgcmVtb3ZlIGNlcnRhaW4gZ2VuZXMuIE1BTEFUMSBpcyBoaWdobHkgZXhwcmVzc2VkIGluIGxvbmcgbm9uLWNvZGluZyBSTkEgYW5kIGlzIHByZXR0eSB1YmlxdWl0b3VzLiBTbyB3ZSBkb250IHdhbnQgdG8gaW5jbHVkZSBhbmQgc3Vic2VxdWVudGx5IGNsdXN0ZXIgYWNjb3JkaW5nIHRvIHRoZSBleHByZXNzaW9uIG9mIHRoaXMgZ2VuZS4gU2ltaWxhcmx5LCBIYiBnZW5lcyBhcmUgaGlnaGx5IGV4cHJlc3NlZCBpbiByYmMsIHdoaWNoIG1heSByZXByZXNlbnQgY29udGFtaW5hdGlvbi4gV2UgY2FuIHJlbW92ZSB0aGVzZSB0b28uIAoKYGBge3J9CmtlZXBfZ2VuZXMgPC0gcm93bmFtZXMoZGF0YSlbIXJvd25hbWVzKGRhdGEpICVpbiUgYyhncmVwKCJNQUxBVDEiLCByb3duYW1lcyhkYXRhKSwgdmFsdWU9VCksIGdyZXAoIl5IQlteKFApXSIsIHJvd25hbWVzKGRhdGEpLCB2YWx1ZT1UKSldCmRhdGEgPC0gc3Vic2V0KGRhdGEsIGZlYXR1cmVzID0ga2VlcF9nZW5lcykKYGBgCgpMb3cgZ2VuZXMgY2FuIGNyZWF0ZSBub2lzZSBhbmQgdGFrZSB1cCB1bm5lY2Vzc2FyeSBzcGFjZSBpbiB0aGUgb2JqZWN0LiBMZXQncyByZW1vdmUgZ2VuZXMgYWNjb3JkaW5nIHRvIHRoZSBvcmlnaW5hbCBtYW51c2NyaXB0LCB3aGVyZWJ5IGEgZ2VuZSBub3QgZm91bmQgaW4gPiAyIHNwb3RzIGlzIHJlbW92ZWQuIFlvdSBjYW4gYWRqdXN0IHRoaXMgYXMgeW91IHdpc2guIAoKYGBge3J9CnRhYmxlKHJvd1N1bXMoZGF0YUBhc3NheXMkU3BhdGlhbEBsYXllcnMkY291bnRzPj0xKT49MikKa2VlcF9nZW5lcyA8LSByb3dTdW1zKGRhdGFAYXNzYXlzJFNwYXRpYWxAbGF5ZXJzJGNvdW50cyA+PSAxKSA+PSAyCiNiZWZvcmUgZmlsdGVyaW5nCmRpbShkYXRhKQpkYXRhIDwtIHN1YnNldChkYXRhLCBmZWF0dXJlcyA9IHJvd25hbWVzKGRhdGEpW2tlZXBfZ2VuZXNdKQojIGFmdGVyIGZpbHRlcmluZwpkaW0oZGF0YSkKYGBgCgo8L2Rpdj4KCjxocj4KCldlIHJlY29tbWVuZCB0YWtpbmcgc29tZSB0aW1lIHRvIGNhcmVmdWxseSBjb25zaWRlciB5b3VyIFFDIHN0cmF0ZWd5LiA8aT4qKlRoZXJlIGlzIG5vIHVuaXZlcnNhbCBnb2xkIHN0YW5kYXJkPC9pPioqLiBPbmUgY29tbW9uIGFwcHJvYWNoIGlzIHRvIHN0YXJ0IHdpdGggbGVuaWVudCBRQywgcHJvY2VlZCB3aXRoIHByb2Nlc3NpbmcsIGFuZCBkdXJpbmcgY2x1c3RlcmluZyBhbmQgYW5ub3RhdGlvbiwgcmVtYWluIGF3YXJlIG9mIHRoZSByZWxheGVkIGZpbHRlcmluZy4gSWYgdW5leHBlY3RlZCByZXN1bHRzIGFyaXNlLCB5b3UgY2FuIHJldmlzaXQgYW5kIHJlZmluZSB5b3VyIFFDLiBQZXJzb25hbGx5LCBJIHByZWZlciB0byBiZSBwZXJtaXNzaXZlIGF0IGZpcnN0LCB3aGlsZSB0YWdnaW5nIHBvdGVudGlhbCBvdXRsaWVycyBmb3IgZnV0dXJlIGNvbnNpZGVyYXRpb24uIEZvciBleGFtcGxlLCB3ZSBpbml0aWFsbHkgZmlsdGVyZWQgb3V0IGdlbmVzIG5vdCBwcmVzZW50IGluIGF0IGxlYXN0IDIgc3BvdHMsIGJ1dCBJIG1heSBhbHNvIGxhYmVsIGdlbmVzIHByZXNlbnQgaW4gZmV3ZXIgdGhhbiA1IHNwb3RzIGFzIOKAnHRvUUPigJ0gZm9yIGxhdGVyIHJldmlld+KAlHBhcnRpY3VsYXJseSB3aGVuIGFzc2Vzc2luZyBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiByZXN1bHRzLiBTaW1pbGFybHksIHdlIGFwcGxpZWQgYSAxNSUgbWl0b2Nob25kcmlhbCB0aHJlc2hvbGQgYnV0IGRpZCBub3QgZmlsdGVyIGJhc2VkIG9uIHJpYm9zb21hbCBjb250ZW50LiBBdCBhIGxhdGVyIHN0YWdlLCB3ZSBjb3VsZCB0aWdodGVuIHRoZSBtaXRvY2hvbmRyaWFsIHRocmVzaG9sZCB0byAxMCUgYW5kIGluY2x1ZGUgYSBmaWx0ZXIgZm9yIGhpZ2ggcmlib3NvbWFsIHNwb3RzLiBJZiB1bnVzdWFsIGNsdXN0ZXJzIGFwcGVhciwgd2UgY2FuIHRoZW4gYXNzZXNzIGhvdyBtYW55IG9mIHRob3NlIGNlbGxzIHdvdWxkIGhhdmUgYmVlbiBleGNsdWRlZCB1bmRlciBzdHJpY3RlciBRQy4KCgojIERhdGEgUHJvY2Vzc2luZ3sudGFic2V0IC50YWJzZXQtZmFkZX0KClNldXJhdCBpcyBhbiBlZmZpY2llbnQgcGFja2FnZSB3aXRoIHNpbXBsZSBmdW5jdGlvbnMgZm9yIHN0YW5kYXJkIHByb2Nlc3NpbmcuIAoKLSBgTm9ybWFsaXplRGF0YWA6IFRoZSBkZWZhdWx0IGlzIHRvIGxvZy1ub3JtYWxpemUgKihGZWF0dXJlIGNvdW50cyBmb3IgZWFjaCBjZWxsIGFyZSBkaXZpZGVkIGJ5IHRoZSB0b3RhbCBjb3VudHMgZm9yIHRoYXQgY2VsbCBhbmQgbXVsdGlwbGllZCBieSB0aGUgc2NhbGUuZmFjdG9yLiBUaGlzIGlzIHRoZW4gbmF0dXJhbC1sb2cgdHJhbnNmb3JtZWQgdXNpbmcgbG9nMXApKi4gU3BhdGlhbCBkYXRhIG1heSBiZSBiZXR0ZXIgbm9ybWFsaXNlZCB3aXRoIEFyZWEvY2VsbCBjb3VudCBjb25zaWRlcmVkLiBBcyB3ZSBkb24ndCBoYXZlIGNlbGwgY291bnRzIHBlciBzcG90IGhlcmUsIHdlJ2xsIHN0aWNrIHRvIHRoZSBzdGFuZGFyZCBhcHByb2FjaCBhbmQgdGhhdCB0YWtlbiBieSB0aGUgYXV0aG9ycyAKCi0gYEZpbmRWYXJpYWJsZUZlYXR1cmVzYDogVGhlIHN0YW5kYXJkIGFwcHJvYWNoIGlzIDIwMDAgdG9wIHZhcmlhYmxlIGdlbmVzICsgJ3ZzdCcgc2VsZWN0aW9uIG1ldGhvZC4gKihGaXJzdCwgZml0cyBhIGxpbmUgdG8gdGhlIHJlbGF0aW9uc2hpcCBvZiBsb2codmFyaWFuY2UpIGFuZCBsb2cobWVhbikgdXNpbmcgbG9jYWwgcG9seW5vbWlhbCByZWdyZXNzaW9uIChsb2VzcykuIFRoZW4gc3RhbmRhcmRpemVzIHRoZSBmZWF0dXJlIHZhbHVlcyB1c2luZyB0aGUgb2JzZXJ2ZWQgbWVhbiBhbmQgZXhwZWN0ZWQgdmFyaWFuY2UgKGdpdmVuIGJ5IHRoZSBmaXR0ZWQgbGluZSkuIEZlYXR1cmUgdmFyaWFuY2UgaXMgdGhlbiBjYWxjdWxhdGVkIG9uIHRoZSBzdGFuZGFyZGl6ZWQgdmFsdWVzIGFmdGVyIGNsaXBwaW5nIHRvIGEgbWF4aW11bSkqLiBTdGFuZGFyZCBpcyAyMDAwIGdlbmVzCgotIGBTY2FsZURhdGFgOiBoZXJlLCB3ZSBzY2FsZSBqdXN0IHRoZSB2YXJpYWJsZSBnZW5lcyB0byBzYXZlIHNwYWNlLiBJZiB5b3UgZmluZCB5b3Vyc2VsZiB1bmFibGUgdG8gdmlzdWFsaXNlIHlvdXIgZ2VuZSBvZiBpbnRlcmVzdCBpbiBzdWJzZXF1ZW50IHZpc3VhbGlzYXRpb25zLCBpdCB3YXMgbm90IGluIHRoaXMgdG9wIHZhcmlhYmxlIGxpc3QuIEluIHRoYXQgY2FzZSwgeW91IGNhbiBzY2FsZSBhbGwgZGF0YS4gCgotIGBSdW5QQ0FgICYgYFJ1blVNQVBgOiBWaXN1YWxpc2F0aW9uIHRvb2xzLiBUaGVyZSBhcmUgYSBmZXcgdGhpbmdzIHRvIGNvbnNpZGVyIGluIHRoZSBVTUFQIGZ1bmN0aW9uLCBidXQgbW9zdCBpbXBvcnRhbnQgaXMgdGhlIGBkaW1zYCBhcmd1bWVudC4gV2UnbGwgY292ZXIgdGhpcyBiZWxvdy4gCgo8ZGl2PgoKIyMgTm9ybWFsaXNhdGlvbgoKQWdhaW4sIG5vcm1hbGlzYXRpb24gaXMgcXVpdGUgc2ltcGxlLiAKCmBgYHtyfQpkYXRhIDwtIE5vcm1hbGl6ZURhdGEoZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJMb2dOb3JtYWxpemUiLAogICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZT1GKQpgYGAKCiMjIFZhcmlhYmxlIEZlYXR1cmVzCgpXZSdsbCBmaW5kIHRoZSB0b3AgdmFyaWFibGUgZmVhdHVyZXMuCgpgYGB7cn0KZGF0YSA8LSBGaW5kVmFyaWFibGVGZWF0dXJlcyhkYXRhLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAndnN0JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmZlYXR1cmVzID0gMjAwMCwgIyBGaW5kIHRoZSAyMDAwIG1vc3QgdmFyaWFibGUgZmVhdHVyZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2ZXJib3NlPUYpCmBgYAoKV2UgY2FuIG91dHB1dC92aXN1YWxpc2UgdGhlbS4gCgpgYGB7cn0KVmFyaWFibGVGZWF0dXJlcyhkYXRhKSAlPiUgaGVhZCg1MCkKCkxhYmVsUG9pbnRzKHBsb3QgPSBWYXJpYWJsZUZlYXR1cmVQbG90KGRhdGEpLCAKICAgICAgICAgICAgcG9pbnRzID0gaGVhZChWYXJpYWJsZUZlYXR1cmVzKGRhdGEpLCA1MCksIHJlcGVsID0gVFJVRSkKYGBgCgojIyBTY2FsZSBEYXRhCgpTY2FsaW5nIHRoZSBkYXRhIGlzIGFsc28gZWFzeSEKCmBgYHtyfQpkYXRhIDwtIFNjYWxlRGF0YShkYXRhLCAKICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSBWYXJpYWJsZUZlYXR1cmVzKGRhdGEpLCAjaWYgeW91IHdhbnQgdG8gc2NhbGUgYWxsIGZlYXR1cmVzLCBjaGFuZ2UgdGhpcyB0byByb3duYW1lcyhkYXRhKQogICAgICAgIHZlcmJvc2U9RikKYGBgCgpJZiB5b3UgbmVlZCB0byByZXR1cm4gdG8geW91ciBhbmFseXNpcyBhdCBhbm90aGVyIHRpbWUsIG9yIHlvdSB3YW50IGEgcmVjb3JkLCB3ZSBjYW4gc2F2ZSB0aGUgcHJvY2Vzc2VkIGRhdGEgaW4gYSBkZWRpY2F0ZWQgZm9sZGVyIGFuZCBsYWJlbCBpdCBhY2NvcmRpbmdseS4KCmBgYHtyLCBldmFsPUZ9CnNhdmVSRFMoZGF0YSwgZmlsZSA9IHBhc3RlMCgiZGF0YS9TZXVyYXRPYmplY3RfIixmb3JtYXQuRGF0ZShTeXMuRGF0ZSgpLCIlWSVtJWQiKSwiX2ZpbHRlcmVkX25vcm1hbGlzZWRfc2NhbGVkLnJkcyIpKQpgYGAKCgojIyBQQ0EgJiBVTUFQCgpBbHNvIHZlcnkgZWFzeSB3aXRoIFNldXJhdC4gV2UnbGwgZGV0ZXJtaW5lIHRoZSBudW1iZXIgb2YgUENzIHRvIHVzZSBmb3IgdGhlIFVNQVAgdXNpbmcgRWxib3dQbG90CgpgYGB7cn0KZGF0YSA8LSBSdW5QQ0EoZGF0YSwKICAgICAgICAgICAgICAgbnBjcyA9IDUwLAogICAgICAgICAgICAgICB2ZXJib3NlPUYpCgpFbGJvd1Bsb3QoZGF0YSkKYGBgCgpUaGUgZWxib3cgcGxvdCBkcm9wcyBvZmYgZHJhc3RpY2FsbHkgYW5kIHBsYXRlYXVzIGF0IH42LCBtZWFuaW5nIHRoZSB2YXJpYXRpb24ganVzdCBiZWNvbWVzIG5vaXN5IGFyb3VuZCBQQzYtb253YXJkcy4gT3IsIG1vcmUgdGhhbiA2IFBDcyBkbyBub3QgcmVwcmVzZW50IHRoZSBkYXRhIGFueSBiZXR0ZXIuIFNvIHdlJ2xsIGp1c3QgdXNlIHRoZSBmaXJzdCA2IHRvIHByb2NlZWQuIAoKYGBge3J9CmRhdGEgPC0gUnVuVU1BUChkYXRhLCAKICAgICAgICAgICAgICAgIGRpbXM9MTo2LCAKICAgICAgICAgICAgICAgIHZlcmJvc2U9RikKYGBgCgo8L2Rpdj48aHI+CgojIFZpc3VhbGlzYXRpb257LnRhYnNldCAudGFic2V0LWZhZGV9CgpUaGVyZSBhcmUgc2V2ZXJhbCB2aXN1YWxpc2F0aW9ucyB3ZSBjYW4gZG86CgoqKkdyYXBoKio6CgotIFVNQVAgdmlzdWFsaXNhdGlvbgoKLSBDbHVzdGVyIGJhc2VkIGV4cHJlc3Npb25zCgoqKlNwYXRpYWwqKjoKCi0gR2VuZXMgb3ZlcmxhaWQgb24gdGhlIHRpc3N1ZQoKLSBNb2R1bGUgc2NvcmVzIG92ZXJsYWlkIG9uIHRoZSB0aXNzdWUKCiMjIFVNQVB7LnRhYnNldCAudGFic2V0LWZhZGV9CgojIyMgVU1BUCBWaXN1YWxpc2F0aW9uCgpXZSdsbCBmaXJzdCBjbHVzdGVyIHRoZSBkYXRhLgoKYGBge3J9CgpkYXRhIDwtIGRhdGEgJT4lIAogIEZpbmROZWlnaGJvcnMoZGltcyA9IDE6NiwgdmVyYm9zZT1GKSAlPiUKICBGaW5kQ2x1c3RlcnMocmVzb2x1dGlvbj0xLCB2ZXJib3NlPUYpCgpgYGAKCkFuZCBub3cgd2UgY2FuIGNyZWF0ZSBhIFVNQVAuIAoKYGBge3J9CnAxPVVNQVBQbG90KGRhdGEsCiAgICAgICAgICAgIGxhYmVsPVQsIGxhYmVsLmJveD1UKSsKICBOb0xlZ2VuZCgpKwogIE5vQXhlcygpKwogIGdndGl0bGUoIjExIENsdXN0ZXJzIikKcDEKYGBgCgo8ZGV0YWlscz48c3VtbWFyeT4qKk9wZW4gdG8gdmlldyBtb3JlIFBsb3R0aW5nIG9wdGlvbnMqKjwvc3VtbWFyeT4KCkhlcmUgYXJlIHNvbWUgYWRkaXRpb25hbCBhcmd1bWVudHMgdGhhdCBjaGFuZ2UgdGhlIGRlZmF1bHQgdmlzdWFsaXNhdGlvbnMuIENob3AgYW5kIGNoYW5nZSB0byBzZWUgaG93IHRoZXkgd29yazoKCmBgYHtyfQpVTUFQUGxvdChkYXRhLCAKICAgICAgICAgcHQuc2l6ZT0yLCAKICAgICAgICAgbGFiZWw9VCwgCiAgICAgICAgIGxhYmVsLnNpemU9MTAsCiAgICAgICAgIGxhYmVsLmJveD1ULAogICAgICAgICBsYWJlbC5jb2xvcj0id2hpdGUiLAogICAgICAgICByZXBlbD1UKSsKICBOb0xlZ2VuZCgpKwogIE5vQXhlcygpKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9IGMoImJsdWU0IiwgJ2JsdWUyJywgJ2xpZ2h0Ymx1ZScsICdncmVlbjInLCAnZ3JlZW40JywnYnJvd24nLCAnb3JhbmdlJywgJ3JlZCcsICdyZWQzJywgJ3JlZDQnLCAnYmxhY2snKSkrCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPSBjKCJibHVlNCIsICdibHVlMicsICdsaWdodGJsdWUnLCAnZ3JlZW4yJywgJ2dyZWVuNCcsJ2Jyb3duJywgJ29yYW5nZScsICdyZWQnLCAncmVkMycsICdyZWQ0JywgJ2JsYWNrJykpCmBgYAoKPC9kZXRhaWxzPgoKIyMjIEZlYXR1cmVwbG90cwoKV2UgY2FuIHZpZXcgdGhlIGdlbmUgZXhwcmVzc2lvbiBvbiB0aGUgVU1BUCBpdHNlbGYuIAoKYGBge3J9CnAyPUZlYXR1cmVQbG90KGRhdGEsIAogICAgICAgICAgICAiQ0QzRSIsCiAgICAgICAgICAgIG9yZGVyPVQsCiAgICAgICAgICAgIHB0LnNpemU9MiwgCiAgICAgICAgICAgIGNvbHMgPSBjKCJ5ZWxsb3cyIiwgJ2JsdWUzJykpK05vQXhlcygpK05vTGVnZW5kKCkKcDM9RmVhdHVyZVBsb3QoZGF0YSwgCiAgICAgICAgICAgICJGMTNBMSIsCiAgICAgICAgICAgIG9yZGVyPVQsCiAgICAgICAgICAgIHB0LnNpemU9MiwgCiAgICAgICAgICAgIGNvbHMgPSBjKCJ5ZWxsb3cyIiwgJ2JsdWUzJykpK05vQXhlcygpK05vTGVnZW5kKCkKcDQ9RmVhdHVyZVBsb3QoZGF0YSwgCiAgICAgICAgICAgICJDT0wxQTEiLAogICAgICAgICAgICBvcmRlcj1ULAogICAgICAgICAgICBwdC5zaXplPTIsIAogICAgICAgICAgICBjb2xzID0gYygieWVsbG93MiIsICdibHVlMycpKStOb0F4ZXMoKStOb0xlZ2VuZCgpCnA1PUZlYXR1cmVQbG90KGRhdGEsIAogICAgICAgICAgICAiS1JUNSIsCiAgICAgICAgICAgIG9yZGVyPVQsCiAgICAgICAgICAgIHB0LnNpemU9MiwKICAgICAgICAgICAgbWluLmN1dG9mZiA9IDMsCiAgICAgICAgICAgIGNvbHMgPSBjKCJ5ZWxsb3cyIiwgJ2JsdWUzJykpK05vQXhlcygpK05vTGVnZW5kKCkKcGxvdF9ncmlkKHAxLCBwbG90X2dyaWQocDIscDMscDQscDUsIG5jb2w9MikpCgojIENhbiBzYXZlIHRoZSBwbG90CnBzYXZlIDwtIHBsb3RfZ3JpZChwMSwgcGxvdF9ncmlkKHAyLHAzLHA0LHA1LCBuY29sPTIpKQpnZ3NhdmUoIkZlYXR1cmVQbG90X1VNQVAucG5nIiwgcGxvdCA9IHBzYXZlLCBwYXRoID0gIi4vcGxvdHMiLCB3aWR0aCA9IDE4LCBoZWlnaHQgPSAxMCwgdW5pdHMgPSBjKCJjbSIpKQpgYGAKYGBge3IsIGVjaG89RkFMU0V9CnJtKHAxLHAyLHAzLHA0LHA1LHBzYXZlKQpgYGAKCiMjIyBERSBhbmQgRG90UGxvdHMKCkFuZCB3ZSBjYW4gc2ltcGx5IGFzc2VzcyB0aGUgY2x1c3RlcnMgdGhlbXNlbHZlcyB1c2luZyBkaWZmZXJlbnRpYWwgYW5hbHlzaXMgYW5kIGV4cHJlc3Npb24gZ3JhcGhzLiAKCmBgYHtyfQptYXJrZXJzIDwtIEZpbmRBbGxNYXJrZXJzKGRhdGEsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGxvZ2ZjLnRocmVzaG9sZCA9IDAuMSwjZGVmYXVsdCAtIGluY3JlYXNpbmcgc3BlZWRzIGl0IHVwLCBidXQgbWF5IG1pc3Mgd2Vha2VyIHNpZ25hbHMuCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWluLnBjdCA9IDAuMDEsICNkZWZhdWx0IC0gY2FuIGJlIGluY3JlYXNlZCB0byBlbnN1cmUgdGhhdCB0aGUgbWluaW11bSBleHByZXNzaW9uIG9mIGdlbmVzIGFyZSBjb25zaWRlcmVkLiAKICAgICAgICAgICAgICAgICAgICAgICAgICBtaW4uZGlmZi5wY3QgPSAtSW5mLCAjIHlvdSBjYW4gYWRqdXN0IHRoaXMgdG9vLiBJRiB5b3Ugd2FudCB0aGVyZSB0byBiZSBhdCBsZWFzdCBhIDEwJSBkaWZmZXJlbmNlIGluIHBlcmNlbnRhZ2Ugb2Ygc3BvdHMgZXhwcmVzc2luZyB0aGUgZ2VuZSwgeW91J2QgY2hhbmdlIHRoaXMgdG8gMC4xLiBDYW4gZW5zdXJlIHRoYXQgaXRzIG5vdCBvbmx5IHRoZSBsZXZlbCBvZiBleHByZXNzaW9uLCBidXQgdGhhdCB5b3UncmUgY2FwdHVyaW5nIGhpZ2hseSBleHByZXNzZWQgZ2VuZXMKICAgICAgICAgICAgICAgICAgICAgICAgICB2ZXJib3NlPUYgI2NoYW5nZSB0aGlzIHRvIHRydWUgaWYgeW91IHdhbnQgdG8gdHJhY2sgdGhlIHNwZWVkIGF0IHdoaWNoIERFIGlzIGJlaW5nIGNhbGN1bGF0ZWQuIEl0IGlzIG9mZiBoZXJlIHRvIHNhdmUgaXQgb3V0cHV0dGluZyBpbnRvIHRoZSBkb2N1bWVudCAKICAgICAgICAgICAgICAgICAgICAgICAgICApIAp0b3AxMCA8LSBtYXJrZXJzICU+JSAKICBncm91cF9ieShjbHVzdGVyKSAlPiUgCiAgdG9wX24od3Q9YXZnX2xvZzJGQywgbj0xMCkKYGBgCgo8ZGV0YWlscz48c3VtbWFyeT5IZWF0bWFwPC9zdW1tYXJ5PgoKYGBge3J9CkRvSGVhdG1hcChBZ2dyZWdhdGVFeHByZXNzaW9uKFNjYWxlRGF0YShkYXRhLCByb3duYW1lcyhkYXRhKSwgdmVyYm9zZT1GKSwgcmV0dXJuLnNldXJhdCA9IFQsIHZlcmJvc2U9RiksCiAgICAgICAgICBmZWF0dXJlcyA9IHRvcDEwJGdlbmUsIAogICAgICAgICAgZHJhdy5saW5lcyA9IEYpCmBgYAoKPGhyPgoKPC9kZXRhaWxzPgoKPGRldGFpbHM+PHN1bW1hcnk+RG90UGxvdDwvc3VtbWFyeT4KCmBgYHtyfQpEb3RQbG90KGRhdGEsIAogICAgICAgIGZlYXR1cmVzID0gYygiQ0QzRSIsICJDTEVDMTBBIiwgIkNEMjA3IiwgIktSVDUiLCAiQ09MMUExIiksIAogICAgICAgIGNvbHM9YygiZ3JleSIsICJyZWQiKSkKYGBgCgo8L2RldGFpbHM+Cgo8ZGV0YWlscz48c3VtbWFyeT5WaW9saW48L3N1bW1hcnk+CgpgYGB7cn0KVmxuUGxvdChkYXRhLCAKICAgICAgICBmZWF0dXJlcyA9IGMoIkNEM0UiLCAiQ0xFQzEwQSIsICJDRDIwNyIsICJLUlQ1IiwgIkNPTDFBMSIpKQpWbG5QbG90KGRhdGEsIAogICAgICAgIGZlYXR1cmVzID0gYygiQ0QzRSIsICJDTEVDMTBBIiwgIkNEMjA3IiwgIktSVDUiLCAiQ09MMUExIiksIHN0YWNrPVQpCmBgYAoKPC9kZXRhaWxzPgoKIyMgU3BhdGlhbHsudGFic2V0IC50YWJzZXQtZmFkZX0KCldlIGNhbiBkbyBhIGZldyB0aGluZ3MgaGVyZS4gCgojIyMgQ2x1c3RlcnMgb3ZlcmxhaWQgc3BhdGlhbGx5CgpgYGB7cn0KU3BhdGlhbERpbVBsb3QoZGF0YSwgcHQuc2l6ZT0zKQpJbWFnZURpbVBsb3QoZGF0YSwgc2l6ZT0zKQpgYGAKCiMjIyBFeHByZXNzaW9ucyBvdmVybGFpZCBzcGF0aWFsbHkKCmBgYHtyfQpTcGF0aWFsRmVhdHVyZVBsb3QoZGF0YSwgCiAgICAgICAgICAgICAgICAgICBmZWF0dXJlcz0iQ0QzRSIsCiAgICAgICAgICAgICAgICAgICBwdC5zaXplPTMpCgpTcGF0aWFsRmVhdHVyZVBsb3QoZGF0YSwgCiAgICAgICAgICAgICAgICAgICBmZWF0dXJlcz0iQ0QzRSIsCiAgICAgICAgICAgICAgICAgICBwdC5zaXplPTMpKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19iKCkKCkltYWdlRmVhdHVyZVBsb3QoZGF0YSwgCiAgICAgICAgICAgICAgICAgICBmZWF0dXJlcz0iQ0QzRSIsCiAgICAgICAgICAgICAgICAgICBzaXplPTMpCmBgYAoKCgoKCgoKCgoKCg==