This is an R Markdown Notebook, to explain the use of the Bioconductor OpenCyto Framework for basic plotting and gating.

In the following I will use exemplary data of phytoplankton cultures, that I gathered during the lab work for my master thesis at the Alfred-Wegener-Institute, under the supervision of Sebastian Rokitta & Björn Rost. In order to perform your own exemplary analysis using my data, you can download it here: Data Download as Zip

Setting gates in FlowJ

The gating of raw data can be done within R, but there is (as far as i know) no good graphical interface to do so, and setting the numerical limits manually is quite a pain. However, there is the possiblity to use FlowJo to do the gating graphically and then read the fully gated workspace into R using the flowWorkspace library.

You can download FlowJo for a free 30-day trial here. Performing the gating is relatively straight-forward, and they have good documentation. What is important to keep in mind is that coherent naming of the gates, and syncing them between your different samples, keeps everything tidy and allows you to easily access your gating hierarchy (from gates to subgates) using R.

Once the Gating is done, you should export your FlowJo file as an XML Workspace (i.e. .xml or .wsp files), so that R can read it. (More on that here)

Start working in R

First of all we have to make sure that all necessary packages are installed:

source("https://bioconductor.org/biocLite.R")
biocLite("openCyto") #this installs the OpenCyto framework
biocLite("ggcyto") #this installs added plotting funtionality

and then load the packages into the workspace:

library(openCyto)
library(ggcyto)

Reading FlowJo Workspace

If all went well, we can start reading our data:

getwd() #this gives you your current working directory
[1] "/Users/bp/Desktop/FlowCytoDATA_bp"

Now you have to change it to the place that you downloaded and unzipped the data:

#change this to the place where the data is
setwd('/Users/bp/Desktop/FlowCytoDATA_bp/')

Now we can read the XML Workspace from FlowJo and start exploring it:

(The official bioconductor documentation for this can be found here)

flowDataPath <- "FlowJoWorkspace_0.2.wsp"
ws <- openWorkspace(flowDataPath)

Now let’s look at the contents of our FlowJo workspace:

print(ws)
FlowJo Workspace Version  20.0 
File location:  . 
File name:  FlowJoWorkspace_0.2.wsp 
Workspace is open. 

Groups in Workspace

Above you can see all the samples in the workspace according to the group names that have been assigned in FlowJo.

Now let’s have a look at a couple of different components of this workspace:

getSamples(ws) #see all samples
getSampleGroups(ws)$groupName #see all groups
 [1] All Samples   All Samples   All Samples   All Samples   All Samples   All Samples   All Samples  
 [8] All Samples   All Samples   All Samples   All Samples   All Samples   All Samples   All Samples  
[15] All Samples   All Samples   All Samples   All Samples   All Samples   All Samples   All Samples  
[22] All Samples   All Samples   All Samples   All Samples   All Samples   All Samples   All Samples  
[29] All Samples   All Samples   All Samples   All Samples   beads         beads         beads        
[36] beads         beads         beads         Micromonas    Micromonas    Micromonas    Micromonas   
[43] Micromonas    Micromonas    Phaeocystis   Phaeocystis   Phaeocystis   Phaeocystis   Phaeocystis  
[50] Phaeocystis   Phaeocystis   Phaeocystis   Phaeocystis   Phaeocystis   Phaeocystis   Phaeocystis  
[57] Phaeocystis   Thalassiosira Thalassiosira Thalassiosira Thalassiosira Thalassiosira Thalassiosira
Levels: All Samples beads Micromonas Phaeocystis Thalassiosira
#access xml keywords
sn <- "A01 MilliQBeads_2211_2200.fcs"
getKeywords(ws, sn)[8]  # <- get $VOL
$`$VOL`
[1] "100015"

For example, this tells me that exactly 100.015 ml were sampled by the Accuri Flow Cytometer according to it’s sensors. You can have a look at the first five XML metadata using:

getKeywords(ws, sn)[1:5]
$`#SPACERS`
[1] "00000"

$`$ENDANALYSIS`
[1] "0"

$`$BEGINANALYSIS`
[1] "0"

$`$ENDDATA`
[1] "38186"

$`$BEGINDATA`
[1] "2570"

parse FCS files within Workspace

As of now, we have only been dealing with metadata, now let’s get started with linking this metadata to our actual FACS sample data. To do this we have to parse the workspace and point it to the correct path that our “.fcs” files reside in.

The parseWorkspace function takes three inputs:

Here I parse the workspace for Name #4, which references Phaeocystis

gs <- parseWorkspace(ws,name = 4, path = '20180219_163432/')
Parsing 13 samples
windows version of flowJo workspace recognized.
version X
Creating ncdfFlowSet...
All FCS files have the same following channels:
FSC-A
SSC-A
FL1-A
FL2-A
FL3-A
FL4-A
FSC-H
SSC-H
FL1-H
FL2-H
FL3-H
FL4-H
Width
Time
done!
loading data: 20180219_163432//D01 EXPIIPh0_2211_2200.fcs
No compensation
gating ...
write D01 EXPIIPh0_2211_2200.fcs_1408 to empty cdf slot...
loading data: 20180219_163432//D02 EXPIIPh0_2311_1700.fcs
No compensation
gating ...
write D02 EXPIIPh0_2311_1700.fcs_1857 to empty cdf slot...
loading data: 20180219_163432//D03 EXPIIPh0_2411_1500.fcs
No compensation
gating ...
write D03 EXPIIPh0_2411_1500.fcs_1608 to empty cdf slot...
loading data: 20180219_163432//D04 EXPIIPh0_2511_1740.fcs
No compensation
gating ...
write D04 EXPIIPh0_2511_1740.fcs_1615 to empty cdf slot...
loading data: 20180219_163432//D05 EXPIIPh0_2611_2000.fcs
No compensation
gating ...
write D05 EXPIIPh0_2611_2000.fcs_2422 to empty cdf slot...
loading data: 20180219_163432//D06 EXPIIPh0_2711_2000.fcs
No compensation
gating ...
write D06 EXPIIPh0_2711_2000.fcs_2526 to empty cdf slot...
loading data: 20180219_163432//G04 EXPIIPhx_2511_1740.fcs
No compensation
gating ...
write G04 EXPIIPhx_2511_1740.fcs_1333 to empty cdf slot...
loading data: 20180219_163432//H01 EXPIIPhx_2211_2200.fcs
No compensation
gating ...
write H01 EXPIIPhx_2211_2200.fcs_1076 to empty cdf slot...
loading data: 20180219_163432//H02 EXPIIPhx_2311_1700.fcs
No compensation
gating ...
write H02 EXPIIPhx_2311_1700.fcs_1372 to empty cdf slot...
loading data: 20180219_163432//H03 EXPIIPhx_2411_1500.fcs
No compensation
gating ...
write H03 EXPIIPhx_2411_1500.fcs_1260 to empty cdf slot...
loading data: 20180219_163432//H04 EXPIIPhx_2511_1740.fcs
No compensation
gating ...
write H04 EXPIIPhx_2511_1740.fcs_1317 to empty cdf slot...
loading data: 20180219_163432//H05 EXPIIPhx_2611_2000.fcs
No compensation
gating ...
write H05 EXPIIPhx_2611_2000.fcs_1560 to empty cdf slot...
loading data: 20180219_163432//H06 EXPIIPhx_2711_2000.fcs
No compensation
gating ...
write H06 EXPIIPhx_2711_2000.fcs_1526 to empty cdf slot...
done!

If all worked correctly you should now be able to access the raw data, and plot the gates on top of the data quite easily:

getPopStats(gs) # <- these are the raw cell counts within the fcs files

Plotting the data with gates

Although this entire framework can seem quite unintuitive at times, when it comes to plotting it is easy to produce some usable results. Here I plot the FCS data in the first sample, together with the gates, which were defined for FL3-A (fluoresence) vs FSC-A (forward scatter)

plotGate(gs[[1]], c("AllPh-beads","normal cells","bigger cells","biggest cells"))

in addition to the plotGate function there is also the useful plotting function autoplot:

plot <- autoplot(gs[[1]], c("normal cells","bigger cells","biggest cells"), bins=100)+ 
            ggcyto_par_set(limits = "data")+
            xlim(c(0.6,1))
plot$`normal cells`

Extracting raw counts from gated data

Make sure to have these other libraries installed:

library(ggplot2)
library(cowplot) # stylesheet for ggplot2 -> makes the plot look much nicer
library(scales) # needed for time conversions later on

Now we can actually start working with the flow counts. The gating set gs contains flow cytometry data from Phaeocystis cultures, hence the Ph abbreviation.

We can extract the count data using the getPopStats function:

PhPop <- getPopStats(gs)[]

And these raw counts are unfortunately a bit messy, so we will convert this into a data frame and clean it up a bit:

PhCounts <- data.frame(PhPop)
PhCounts <- PhCounts[PhCounts$name != "G04 EXPIIPhx_2511_1740.fcs_1333",] #remove one measurement which was botched and repeated
#the following three lines clean up the data frame structure (in particular the row names) after deletion of one sample
PhCounts <- PhCounts[with(PhCounts, order(name)), ]
row.names(PhCounts) <- 1:nrow(PhCounts)
PhnumGate = length(levels((factor(PhCounts$Population))))

Now that we have extracted the raw data and put it into a usable data frame format, we can normalize all counts using the control bead counts, that were added to every sample. For this I take a subset of my samples (one experimental run), then calculate the mean bead concentration of the samples and divide all other counts by it. (note: this might not be the best way to do it)

Ph0Counts <- PhCounts[1:(PhnumGate*6),] #this takes the subset (one experimental run)
meanBeadsPerML_Ph0 <- mean(Ph0Counts[Ph0Counts$Population == "beads",]$Count) * 10 #this gives the mean beads per ml
# now we have to normalize all values with the mean bead concentration:
Ph0Counts$NormCount <- Ph0Counts$Count / rep(Ph0Counts[Ph0Counts$Population == "beads",]$Count,each=PhnumGate) * meanBeadsPerML_Ph0
#and finally we can show the data.frame with the normalized counts:
Ph0Counts[,c(1,2,6)]

To this data, I will add the time of sampling, which i noted down in my labbook, and saved in a csv file:

FloCounts <- read.csv('FloCountsRough.csv')
FloCounts$Time <- as.POSIXct(FloCounts$Time,format="%d.%m.%Y %H:%M:%S")
FloCounts$Time <- FloCounts$Time-1800000 #converting actual date into number of days
TimeVector <- FloCounts$Time[1:6]
Ph0Counts$Time <- rep(TimeVector,each=PhnumGate) #add time to all gates & counts, this is why the structure had to be corrected before

Now that we have normalized counts and time for one species over 6 days, we get to plotting it. To do this in ggplot2, we have to clean the data up some more and convert it into a long format (which is done by data.table::melt).

Ph0Counts_filt <- Ph0Counts[Ph0Counts$Population %in% c('normal cells','bigger cells','biggest cells'),]
Ph0Data <- data.table::melt(Ph0Counts_filt, id.vars = c("Population", "Time")
                           , measure.vars = c("NormCount")) 
Ph0Data$Population <- factor(Ph0Data$Population, levels = c("biggest cells","bigger cells","normal cells"))

Finally, this allows us to plot the normalized cell counts over time, overlayed with the size fraction as taken from Forward Scatter and Fluoresence data. (Which luckily here, does not show too big of a trend towards aggregation of cells)

ggplot(data = Ph0Data, aes(x = Time, y = value, fill = Population)) +
  geom_area() + ggtitle('Ph0 FlowCount + Size Fraction')+
  scale_x_datetime(labels=date_format('%d'), date_breaks = '1 day')+ xlab('days') + ylab('cells/ml')

Additionally:

The bioconductor OpenCyto Framework has much more functions and packages which i did not use.. for example there are extensive capabilites for calculating spillover matrices and normalizing fluoresence data (which often requires a careful experimental setup, including multiple normalization beads).

For now, I hope this serves as a quick overview of the capabilites, feel free to contact me for specific questions at benjamin.post@leibniz-zmt.de

LS0tCnRpdGxlOiAiRmxvdy1DeXRvIERhdGEgaW4gUiwgdXNpbmcgdGhlIEJpb2NvbmR1Y3RvciBPcGVuQ3l0byBGcmFtZXdvcmsiCm91dHB1dDogaHRtbF9ub3RlYm9vawphdXRob3I6IEJlbmphbWluIFBvc3QKLS0tCgpUaGlzIGlzIGFuIFtSIE1hcmtkb3duXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tKSBOb3RlYm9vaywgdG8gZXhwbGFpbiB0aGUgdXNlIG9mIHRoZSBCaW9jb25kdWN0b3IgT3BlbkN5dG8gRnJhbWV3b3JrIGZvciBiYXNpYyBwbG90dGluZyBhbmQgZ2F0aW5nLgoKSW4gdGhlIGZvbGxvd2luZyBJIHdpbGwgdXNlIGV4ZW1wbGFyeSBkYXRhIG9mIHBoeXRvcGxhbmt0b24gY3VsdHVyZXMsIHRoYXQgSSBnYXRoZXJlZCBkdXJpbmcgdGhlIGxhYiB3b3JrIGZvciBteSBtYXN0ZXIgdGhlc2lzIGF0IHRoZSBBbGZyZWQtV2VnZW5lci1JbnN0aXR1dGUsIHVuZGVyIHRoZSBzdXBlcnZpc2lvbiBvZiBTZWJhc3RpYW4gUm9raXR0YSAmIEJqw7ZybiBSb3N0LgpJbiBvcmRlciB0byBwZXJmb3JtIHlvdXIgb3duIGV4ZW1wbGFyeSBhbmFseXNpcyB1c2luZyBteSBkYXRhLCB5b3UgY2FuIGRvd25sb2FkIGl0IGhlcmU6IFtEYXRhIERvd25sb2FkIGFzIFppcF0oaHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2ZpbGUvZC8xalRrWDVYTklhOG85UEdSTUxmUmdSQVFEUUlEZXBEU3cvdmlldz91c3A9c2hhcmluZykKCgojIyBTZXR0aW5nIGdhdGVzIGluIEZsb3dKClRoZSBnYXRpbmcgb2YgcmF3IGRhdGEgY2FuIGJlIGRvbmUgd2l0aGluIFIsIGJ1dCB0aGVyZSBpcyAoYXMgZmFyIGFzIGkga25vdykgbm8gZ29vZCBncmFwaGljYWwgaW50ZXJmYWNlIHRvIGRvIHNvLCBhbmQgc2V0dGluZyB0aGUgbnVtZXJpY2FsIGxpbWl0cyBtYW51YWxseSBpcyBxdWl0ZSBhIHBhaW4uIEhvd2V2ZXIsIHRoZXJlIGlzIHRoZSBwb3NzaWJsaXR5IHRvIHVzZSBbRmxvd0pvXShodHRwczovL3d3dy5mbG93am8uY29tKSB0byBkbyB0aGUgZ2F0aW5nIGdyYXBoaWNhbGx5IGFuZCB0aGVuIHJlYWQgdGhlIGZ1bGx5IGdhdGVkIHdvcmtzcGFjZSBpbnRvIFIgdXNpbmcgdGhlICpmbG93V29ya3NwYWNlKiBsaWJyYXJ5LgoKWW91IGNhbiBkb3dubG9hZCBGbG93Sm8gZm9yIGEgZnJlZSAzMC1kYXkgdHJpYWwgW2hlcmVdKGh0dHBzOi8vd3d3LmZsb3dqby5jb20vc29sdXRpb25zL2Zsb3dqby9mcmVlLXRyaWFsKS4KUGVyZm9ybWluZyB0aGUgZ2F0aW5nIGlzIHJlbGF0aXZlbHkgc3RyYWlnaHQtZm9yd2FyZCwgYW5kIHRoZXkgaGF2ZSBnb29kIFtkb2N1bWVudGF0aW9uXShodHRwOi8vZG9jcy5mbG93am8uY29tL3Z4L2dyYXBocy1hbmQtZ2F0aW5nL2d3LWdhdGluZy8pLiBXaGF0IGlzIGltcG9ydGFudCB0byBrZWVwIGluIG1pbmQgaXMgdGhhdCBjb2hlcmVudCBuYW1pbmcgb2YgdGhlIGdhdGVzLCBhbmQgc3luY2luZyB0aGVtIGJldHdlZW4geW91ciBkaWZmZXJlbnQgc2FtcGxlcywga2VlcHMgZXZlcnl0aGluZyB0aWR5IGFuZCBhbGxvd3MgeW91IHRvIGVhc2lseSBhY2Nlc3MgeW91ciBnYXRpbmcgaGllcmFyY2h5IChmcm9tIGdhdGVzIHRvIHN1YmdhdGVzKSB1c2luZyBSLgoKT25jZSB0aGUgR2F0aW5nIGlzIGRvbmUsIHlvdSBzaG91bGQgZXhwb3J0IHlvdXIgRmxvd0pvIGZpbGUgYXMgYW4gWE1MIFdvcmtzcGFjZSAoaS5lLiAueG1sIG9yIC53c3AgZmlsZXMpLCBzbyB0aGF0IFIgY2FuIHJlYWQgaXQuIChNb3JlIG9uIHRoYXQgW2hlcmVdKGh0dHA6Ly9kb2NzLmZsb3dqby5jb20vZDIvd29ya3NwYWNlcy1hbmQtc2FtcGxlcy9yaWJib25zLWFuZC10YWJzL3dzLXJpYmJvbi1iYW5kLWRlYnVnL3Z4d29ya3NwYWNlcy1hbmQtc2FtcGxlc3JpYmJvbnMtYW5kLXRhYnN3cy1yaWJib24tYmFuZC1kZWJ1Z3dvcmtzcGFjZS14bWwvKSkKCiMjIFN0YXJ0IHdvcmtpbmcgaW4gUgpGaXJzdCBvZiBhbGwgd2UgaGF2ZSB0byBtYWtlIHN1cmUgdGhhdCBhbGwgbmVjZXNzYXJ5IHBhY2thZ2VzIGFyZSBpbnN0YWxsZWQ6CmBgYHtyLGVjaG89VCwgZXZhbD1GfQpzb3VyY2UoImh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9iaW9jTGl0ZS5SIikKYmlvY0xpdGUoIm9wZW5DeXRvIikgI3RoaXMgaW5zdGFsbHMgdGhlIE9wZW5DeXRvIGZyYW1ld29yawpiaW9jTGl0ZSgiZ2djeXRvIikgI3RoaXMgaW5zdGFsbHMgYWRkZWQgcGxvdHRpbmcgZnVudGlvbmFsaXR5CmBgYAphbmQgdGhlbiBsb2FkIHRoZSBwYWNrYWdlcyBpbnRvIHRoZSB3b3Jrc3BhY2U6CmBgYHtyfQpsaWJyYXJ5KG9wZW5DeXRvKQpsaWJyYXJ5KGdnY3l0bykKCmBgYAoKIyMgUmVhZGluZyBGbG93Sm8gV29ya3NwYWNlCklmIGFsbCB3ZW50IHdlbGwsIHdlIGNhbiBzdGFydCByZWFkaW5nIG91ciBkYXRhOgpgYGB7cn0KZ2V0d2QoKSAjdGhpcyBnaXZlcyB5b3UgeW91ciBjdXJyZW50IHdvcmtpbmcgZGlyZWN0b3J5CmBgYApOb3cgeW91IGhhdmUgdG8gY2hhbmdlIGl0IHRvIHRoZSBwbGFjZSB0aGF0IHlvdSBkb3dubG9hZGVkIGFuZCB1bnppcHBlZCB0aGUgZGF0YToKCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30KI2NoYW5nZSB0aGlzIHRvIHRoZSBwbGFjZSB3aGVyZSB0aGUgZGF0YSBpcwpzZXR3ZCgnL1VzZXJzL2JwL0Rlc2t0b3AvRmxvd0N5dG9EQVRBX2JwLycpCmBgYAoKTm93IHdlIGNhbiByZWFkIHRoZSBYTUwgV29ya3NwYWNlIGZyb20gRmxvd0pvIGFuZCBzdGFydCBleHBsb3JpbmcgaXQ6CgooVGhlIG9mZmljaWFsIGJpb2NvbmR1Y3RvciBkb2N1bWVudGF0aW9uIGZvciB0aGlzIGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cHM6Ly93d3cuYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9kZXZlbC9iaW9jL3ZpZ25ldHRlcy9mbG93V29ya3NwYWNlL2luc3QvZG9jL2Zsb3dXb3Jrc3BhY2UtSW50cm9kdWN0aW9uLmh0bWwpKQpgYGB7cn0KZmxvd0RhdGFQYXRoIDwtICJGbG93Sm9Xb3Jrc3BhY2VfMC4yLndzcCIKd3MgPC0gb3BlbldvcmtzcGFjZShmbG93RGF0YVBhdGgpCmBgYAoKTm93IGxldCdzIGxvb2sgYXQgdGhlIGNvbnRlbnRzIG9mIG91ciBGbG93Sm8gd29ya3NwYWNlOgpgYGB7cn0KcHJpbnQod3MpCmBgYApBYm92ZSB5b3UgY2FuIHNlZSBhbGwgdGhlIHNhbXBsZXMgaW4gdGhlIHdvcmtzcGFjZSBhY2NvcmRpbmcgdG8gdGhlIGdyb3VwIG5hbWVzIHRoYXQgaGF2ZSBiZWVuIGFzc2lnbmVkIGluIEZsb3dKby4KCk5vdyBsZXQncyBoYXZlIGEgbG9vayBhdCBhIGNvdXBsZSBvZiBkaWZmZXJlbnQgY29tcG9uZW50cyBvZiB0aGlzIHdvcmtzcGFjZToKYGBge3J9CmdldFNhbXBsZXMod3MpICNzZWUgYWxsIHNhbXBsZXMKZ2V0U2FtcGxlR3JvdXBzKHdzKSRncm91cE5hbWUgI3NlZSBhbGwgZ3JvdXBzCgojYWNjZXNzIHhtbCBrZXl3b3JkcwpzbiA8LSAiQTAxIE1pbGxpUUJlYWRzXzIyMTFfMjIwMC5mY3MiCmdldEtleXdvcmRzKHdzLCBzbilbOF0gICMgPC0gZ2V0ICRWT0wKYGBgCkZvciBleGFtcGxlLCB0aGlzIHRlbGxzIG1lIHRoYXQgZXhhY3RseSAxMDAuMDE1IG1sIHdlcmUgc2FtcGxlZCBieSB0aGUgQWNjdXJpIEZsb3cgQ3l0b21ldGVyIGFjY29yZGluZyB0byBpdCdzIHNlbnNvcnMuIFlvdSBjYW4gaGF2ZSBhIGxvb2sgYXQgdGhlIGZpcnN0IGZpdmUgWE1MIG1ldGFkYXRhIHVzaW5nOgpgYGB7cn0KZ2V0S2V5d29yZHMod3MsIHNuKVsxOjVdCmBgYAojIyBwYXJzZSBGQ1MgZmlsZXMgd2l0aGluIFdvcmtzcGFjZQpBcyBvZiBub3csIHdlIGhhdmUgb25seSBiZWVuIGRlYWxpbmcgd2l0aCAqbWV0YWRhdGEqLCBub3cgbGV0J3MgZ2V0IHN0YXJ0ZWQgd2l0aCBsaW5raW5nIHRoaXMgbWV0YWRhdGEgdG8gb3VyIGFjdHVhbCBGQUNTIHNhbXBsZSBkYXRhLiBUbyBkbyB0aGlzIHdlIGhhdmUgdG8gcGFyc2UgdGhlIHdvcmtzcGFjZSBhbmQgcG9pbnQgaXQgdG8gdGhlIGNvcnJlY3QgcGF0aCB0aGF0IG91ciAiLmZjcyIgZmlsZXMgcmVzaWRlIGluLgoKVGhlIGBgcGFyc2VXb3Jrc3BhY2VgYCBmdW5jdGlvbiB0YWtlcyB0aHJlZSBpbnB1dHM6CgoqIHdzID0gb3VyIEZsb3dKbyBYTUwgd29ya3NwYWNlCgoqIG5hbWUgPSBncm91cCB3aXRoaW4gd29ya3NwYWNlLCBnZXQgbnVtYmVyIGZyb20gYGBgcHJpbnQod3MpYGBgCgoqIHBhdGggPSBmb2xkZXIgaW5jbHVkaW5nIGFsbCAiLmZzYyIgZmlsZXMgcmVmZXJlbmNlZCB3aXRoaW4geW91ciB3b3Jrc3BhY2UKCkhlcmUgSSBwYXJzZSB0aGUgd29ya3NwYWNlIGZvciBOYW1lICM0LCB3aGljaCByZWZlcmVuY2VzIFBoYWVvY3lzdGlzCgpgYGB7cn0KZ3MgPC0gcGFyc2VXb3Jrc3BhY2Uod3MsbmFtZSA9IDQsIHBhdGggPSAnMjAxODAyMTlfMTYzNDMyLycpCmBgYAoKSWYgYWxsIHdvcmtlZCBjb3JyZWN0bHkgeW91IHNob3VsZCBub3cgYmUgYWJsZSB0byBhY2Nlc3MgdGhlIHJhdyBkYXRhLCBhbmQgcGxvdCB0aGUgZ2F0ZXMgb24gdG9wIG9mIHRoZSBkYXRhIHF1aXRlIGVhc2lseToKCmBgYHtyfQpnZXRQb3BTdGF0cyhncykgIyA8LSB0aGVzZSBhcmUgdGhlIHJhdyBjZWxsIGNvdW50cyB3aXRoaW4gdGhlIGZjcyBmaWxlcwpgYGAKI1Bsb3R0aW5nIHRoZSBkYXRhIHdpdGggZ2F0ZXMKQWx0aG91Z2ggdGhpcyBlbnRpcmUgZnJhbWV3b3JrIGNhbiBzZWVtIHF1aXRlIHVuaW50dWl0aXZlIGF0IHRpbWVzLCB3aGVuIGl0IGNvbWVzIHRvIHBsb3R0aW5nIGl0IGlzIGVhc3kgdG8gcHJvZHVjZSBzb21lIHVzYWJsZSByZXN1bHRzLiBIZXJlIEkgcGxvdCB0aGUgRkNTIGRhdGEgaW4gdGhlIGZpcnN0IHNhbXBsZSwgdG9nZXRoZXIgd2l0aCB0aGUgZ2F0ZXMsIHdoaWNoIHdlcmUgZGVmaW5lZCBmb3IgRkwzLUEgKGZsdW9yZXNlbmNlKSB2cyBGU0MtQSAoZm9yd2FyZCBzY2F0dGVyKQpgYGB7cn0KcGxvdEdhdGUoZ3NbWzFdXSwgYygiQWxsUGgtYmVhZHMiLCJub3JtYWwgY2VsbHMiLCJiaWdnZXIgY2VsbHMiLCJiaWdnZXN0IGNlbGxzIikpCmBgYAoKaW4gYWRkaXRpb24gdG8gdGhlIGBgYHBsb3RHYXRlYGBgIGZ1bmN0aW9uIHRoZXJlIGlzIGFsc28gdGhlIHVzZWZ1bCBwbG90dGluZyBmdW5jdGlvbiBgYGBhdXRvcGxvdGBgYDoKCmBgYHtyfQpwbG90IDwtIGF1dG9wbG90KGdzW1sxXV0sIGMoIm5vcm1hbCBjZWxscyIsImJpZ2dlciBjZWxscyIsImJpZ2dlc3QgY2VsbHMiKSwgYmlucz0xMDApKyAKICAgICAgICAgICAgZ2djeXRvX3Bhcl9zZXQobGltaXRzID0gImRhdGEiKSsKICAgICAgICAgICAgeGxpbShjKDAuNiwxKSkKcGxvdCRgbm9ybWFsIGNlbGxzYApgYGAKCiNFeHRyYWN0aW5nIHJhdyBjb3VudHMgZnJvbSBnYXRlZCBkYXRhCgpNYWtlIHN1cmUgdG8gaGF2ZSB0aGVzZSBvdGhlciBsaWJyYXJpZXMgaW5zdGFsbGVkOgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGNvd3Bsb3QpICMgc3R5bGVzaGVldCBmb3IgZ2dwbG90MiAtPiBtYWtlcyB0aGUgcGxvdCBsb29rIG11Y2ggbmljZXIKbGlicmFyeShzY2FsZXMpICMgbmVlZGVkIGZvciB0aW1lIGNvbnZlcnNpb25zIGxhdGVyIG9uCmBgYAoKTm93IHdlIGNhbiBhY3R1YWxseSBzdGFydCB3b3JraW5nIHdpdGggdGhlIGZsb3cgY291bnRzLiBUaGUgZ2F0aW5nIHNldCBgYGBnc2BgYCBjb250YWlucyBmbG93IGN5dG9tZXRyeSBkYXRhIGZyb20gUGhhZW9jeXN0aXMgY3VsdHVyZXMsIGhlbmNlIHRoZSBQaCBhYmJyZXZpYXRpb24uCgpXZSBjYW4gZXh0cmFjdCB0aGUgY291bnQgZGF0YSB1c2luZyB0aGUgYGBgZ2V0UG9wU3RhdHNgYGAgZnVuY3Rpb246CmBgYHtyfQpQaFBvcCA8LSBnZXRQb3BTdGF0cyhncylbXQpgYGAKQW5kIHRoZXNlIHJhdyBjb3VudHMgYXJlIHVuZm9ydHVuYXRlbHkgYSBiaXQgbWVzc3ksIHNvIHdlIHdpbGwgY29udmVydCB0aGlzIGludG8gYSBkYXRhIGZyYW1lIGFuZCBjbGVhbiBpdCB1cCBhIGJpdDoKYGBge3J9ClBoQ291bnRzIDwtIGRhdGEuZnJhbWUoUGhQb3ApClBoQ291bnRzIDwtIFBoQ291bnRzW1BoQ291bnRzJG5hbWUgIT0gIkcwNCBFWFBJSVBoeF8yNTExXzE3NDAuZmNzXzEzMzMiLF0gI3JlbW92ZSBvbmUgbWVhc3VyZW1lbnQgd2hpY2ggd2FzIGJvdGNoZWQgYW5kIHJlcGVhdGVkCgojdGhlIGZvbGxvd2luZyB0aHJlZSBsaW5lcyBjbGVhbiB1cCB0aGUgZGF0YSBmcmFtZSBzdHJ1Y3R1cmUgKGluIHBhcnRpY3VsYXIgdGhlIHJvdyBuYW1lcykgYWZ0ZXIgZGVsZXRpb24gb2Ygb25lIHNhbXBsZQpQaENvdW50cyA8LSBQaENvdW50c1t3aXRoKFBoQ291bnRzLCBvcmRlcihuYW1lKSksIF0Kcm93Lm5hbWVzKFBoQ291bnRzKSA8LSAxOm5yb3coUGhDb3VudHMpClBobnVtR2F0ZSA9IGxlbmd0aChsZXZlbHMoKGZhY3RvcihQaENvdW50cyRQb3B1bGF0aW9uKSkpKQpgYGAKTm93IHRoYXQgd2UgaGF2ZSBleHRyYWN0ZWQgdGhlIHJhdyBkYXRhIGFuZCBwdXQgaXQgaW50byBhIHVzYWJsZSBkYXRhIGZyYW1lIGZvcm1hdCwgd2UgY2FuIG5vcm1hbGl6ZSBhbGwgY291bnRzIHVzaW5nIHRoZSBjb250cm9sIGJlYWQgY291bnRzLCB0aGF0IHdlcmUgYWRkZWQgdG8gZXZlcnkgc2FtcGxlLiBGb3IgdGhpcyBJIHRha2UgYSBzdWJzZXQgb2YgbXkgc2FtcGxlcyAob25lIGV4cGVyaW1lbnRhbCBydW4pLCB0aGVuIGNhbGN1bGF0ZSB0aGUgbWVhbiBiZWFkIGNvbmNlbnRyYXRpb24gb2YgdGhlIHNhbXBsZXMgYW5kIGRpdmlkZSBhbGwgb3RoZXIgY291bnRzIGJ5IGl0LiAqKG5vdGU6IHRoaXMgbWlnaHQgbm90IGJlIHRoZSBiZXN0IHdheSB0byBkbyBpdCkqCmBgYHtyfQpQaDBDb3VudHMgPC0gUGhDb3VudHNbMTooUGhudW1HYXRlKjYpLF0gI3RoaXMgdGFrZXMgdGhlIHN1YnNldCAob25lIGV4cGVyaW1lbnRhbCBydW4pCgoKbWVhbkJlYWRzUGVyTUxfUGgwIDwtIG1lYW4oUGgwQ291bnRzW1BoMENvdW50cyRQb3B1bGF0aW9uID09ICJiZWFkcyIsXSRDb3VudCkgKiAxMCAjdGhpcyBnaXZlcyB0aGUgbWVhbiBiZWFkcyBwZXIgbWwKIyBub3cgd2UgaGF2ZSB0byBub3JtYWxpemUgYWxsIHZhbHVlcyB3aXRoIHRoZSBtZWFuIGJlYWQgY29uY2VudHJhdGlvbjoKUGgwQ291bnRzJE5vcm1Db3VudCA8LSBQaDBDb3VudHMkQ291bnQgLyByZXAoUGgwQ291bnRzW1BoMENvdW50cyRQb3B1bGF0aW9uID09ICJiZWFkcyIsXSRDb3VudCxlYWNoPVBobnVtR2F0ZSkgKiBtZWFuQmVhZHNQZXJNTF9QaDAKI2FuZCBmaW5hbGx5IHdlIGNhbiBzaG93IHRoZSBkYXRhLmZyYW1lIHdpdGggdGhlIG5vcm1hbGl6ZWQgY291bnRzOgpQaDBDb3VudHNbLGMoMSwyLDYpXQpgYGAKVG8gdGhpcyBkYXRhLCBJIHdpbGwgYWRkIHRoZSB0aW1lIG9mIHNhbXBsaW5nLCB3aGljaCBpIG5vdGVkIGRvd24gaW4gbXkgbGFiYm9vaywgYW5kIHNhdmVkIGluIGEgY3N2IGZpbGU6CmBgYHtyfQpGbG9Db3VudHMgPC0gcmVhZC5jc3YoJ0Zsb0NvdW50c1JvdWdoLmNzdicpCgpGbG9Db3VudHMkVGltZSA8LSBhcy5QT1NJWGN0KEZsb0NvdW50cyRUaW1lLGZvcm1hdD0iJWQuJW0uJVkgJUg6JU06JVMiKQpGbG9Db3VudHMkVGltZSA8LSBGbG9Db3VudHMkVGltZS0xODAwMDAwICNjb252ZXJ0aW5nIGFjdHVhbCBkYXRlIGludG8gbnVtYmVyIG9mIGRheXMKVGltZVZlY3RvciA8LSBGbG9Db3VudHMkVGltZVsxOjZdCgpQaDBDb3VudHMkVGltZSA8LSByZXAoVGltZVZlY3RvcixlYWNoPVBobnVtR2F0ZSkgI2FkZCB0aW1lIHRvIGFsbCBnYXRlcyAmIGNvdW50cywgdGhpcyBpcyB3aHkgdGhlIHN0cnVjdHVyZSBoYWQgdG8gYmUgY29ycmVjdGVkIGJlZm9yZQpgYGAKTm93IHRoYXQgd2UgaGF2ZSBub3JtYWxpemVkIGNvdW50cyBhbmQgdGltZSBmb3Igb25lIHNwZWNpZXMgb3ZlciA2IGRheXMsIHdlIGdldCB0byBwbG90dGluZyBpdC4gVG8gZG8gdGhpcyBpbiBnZ3Bsb3QyLCB3ZSBoYXZlIHRvIGNsZWFuIHRoZSBkYXRhIHVwIHNvbWUgbW9yZSBhbmQgY29udmVydCBpdCBpbnRvIGEgbG9uZyBmb3JtYXQgKHdoaWNoIGlzIGRvbmUgYnkgYGBgZGF0YS50YWJsZTo6bWVsdGBgYCkuCmBgYHtyfQpQaDBDb3VudHNfZmlsdCA8LSBQaDBDb3VudHNbUGgwQ291bnRzJFBvcHVsYXRpb24gJWluJSBjKCdub3JtYWwgY2VsbHMnLCdiaWdnZXIgY2VsbHMnLCdiaWdnZXN0IGNlbGxzJyksXQoKUGgwRGF0YSA8LSBkYXRhLnRhYmxlOjptZWx0KFBoMENvdW50c19maWx0LCBpZC52YXJzID0gYygiUG9wdWxhdGlvbiIsICJUaW1lIikKICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBtZWFzdXJlLnZhcnMgPSBjKCJOb3JtQ291bnQiKSkgCgpQaDBEYXRhJFBvcHVsYXRpb24gPC0gZmFjdG9yKFBoMERhdGEkUG9wdWxhdGlvbiwgbGV2ZWxzID0gYygiYmlnZ2VzdCBjZWxscyIsImJpZ2dlciBjZWxscyIsIm5vcm1hbCBjZWxscyIpKQpgYGAKRmluYWxseSwgdGhpcyBhbGxvd3MgdXMgdG8gcGxvdCB0aGUgbm9ybWFsaXplZCBjZWxsIGNvdW50cyBvdmVyIHRpbWUsIG92ZXJsYXllZCB3aXRoIHRoZSBzaXplIGZyYWN0aW9uIGFzIHRha2VuIGZyb20gRm9yd2FyZCBTY2F0dGVyIGFuZCBGbHVvcmVzZW5jZSBkYXRhLiAoV2hpY2ggbHVja2lseSBoZXJlLCBkb2VzIG5vdCBzaG93IHRvbyBiaWcgb2YgYSB0cmVuZCB0b3dhcmRzIGFnZ3JlZ2F0aW9uIG9mIGNlbGxzKQpgYGB7cn0KZ2dwbG90KGRhdGEgPSBQaDBEYXRhLCBhZXMoeCA9IFRpbWUsIHkgPSB2YWx1ZSwgZmlsbCA9IFBvcHVsYXRpb24pKSArCiAgZ2VvbV9hcmVhKCkgKyBnZ3RpdGxlKCdQaDAgRmxvd0NvdW50ICsgU2l6ZSBGcmFjdGlvbicpKwogIHNjYWxlX3hfZGF0ZXRpbWUobGFiZWxzPWRhdGVfZm9ybWF0KCclZCcpLCBkYXRlX2JyZWFrcyA9ICcxIGRheScpKyB4bGFiKCdkYXlzJykgKyB5bGFiKCdjZWxscy9tbCcpCmBgYAoKIyMjIEFkZGl0aW9uYWxseToKVGhlIGJpb2NvbmR1Y3RvciBPcGVuQ3l0byBGcmFtZXdvcmsgaGFzIG11Y2ggbW9yZSBmdW5jdGlvbnMgYW5kIHBhY2thZ2VzIHdoaWNoIGkgZGlkIG5vdCB1c2UuLiBmb3IgZXhhbXBsZSB0aGVyZSBhcmUgZXh0ZW5zaXZlIGNhcGFiaWxpdGVzIGZvciBjYWxjdWxhdGluZyBzcGlsbG92ZXIgbWF0cmljZXMgYW5kIG5vcm1hbGl6aW5nIGZsdW9yZXNlbmNlIGRhdGEgKHdoaWNoIG9mdGVuIHJlcXVpcmVzIGEgY2FyZWZ1bCBleHBlcmltZW50YWwgc2V0dXAsIGluY2x1ZGluZyBtdWx0aXBsZSBub3JtYWxpemF0aW9uIGJlYWRzKS4KCkZvciBub3csIEkgaG9wZSB0aGlzIHNlcnZlcyBhcyBhIHF1aWNrIG92ZXJ2aWV3IG9mIHRoZSBjYXBhYmlsaXRlcywgZmVlbCBmcmVlIHRvIGNvbnRhY3QgbWUgZm9yIHNwZWNpZmljIHF1ZXN0aW9ucyBhdApiZW5qYW1pbi5wb3N0QGxlaWJuaXotem10LmRl